Update:Give full permissions to admin users except updating root or viewing root api token #137

This commit is contained in:
advplyr 2022-05-03 19:16:16 -05:00
parent 195a30096f
commit 2e070227ab
17 changed files with 75 additions and 71 deletions

View File

@ -30,7 +30,7 @@
<span class="material-icons" aria-label="Upload Media" role="button">upload</span> <span class="material-icons" aria-label="Upload Media" role="button">upload</span>
</nuxt-link> </nuxt-link>
<nuxt-link v-if="isRootUser" to="/config" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1"> <nuxt-link v-if="userIsAdminOrUp" to="/config" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
<span class="material-icons" aria-label="System Settings" role="button">settings</span> <span class="material-icons" aria-label="System Settings" role="button">settings</span>
</nuxt-link> </nuxt-link>
@ -100,8 +100,8 @@ export default {
user() { user() {
return this.$store.state.user.user return this.$store.state.user.user
}, },
isRootUser() { userIsAdminOrUp() {
return this.$store.getters['user/getIsRoot'] return this.$store.getters['user/getIsAdminOrUp']
}, },
username() { username() {
return this.user ? this.user.username : 'err' return this.user ? this.user.username : 'err'

View File

@ -7,9 +7,9 @@
<div class="rounded-full py-1 bg-primary hover:bg-bg cursor-pointer px-2 border border-black-100 text-center flex items-center box-shadow-md" @mousedown.prevent @mouseup.prevent @click="showBookshelfTextureModal"><p class="text-sm py-0.5">Texture</p></div> <div class="rounded-full py-1 bg-primary hover:bg-bg cursor-pointer px-2 border border-black-100 text-center flex items-center box-shadow-md" @mousedown.prevent @mouseup.prevent @click="showBookshelfTextureModal"><p class="text-sm py-0.5">Texture</p></div>
</div> </div>
<div v-if="loaded && !shelves.length && isRootUser && !search" class="w-full flex flex-col items-center justify-center py-12"> <div v-if="loaded && !shelves.length && !search" class="w-full flex flex-col items-center justify-center py-12">
<p class="text-center text-2xl font-book mb-4 py-4">{{ libraryName }} Library is empty!</p> <p class="text-center text-2xl font-book mb-4 py-4">{{ libraryName }} Library is empty!</p>
<div class="flex"> <div v-if="userIsAdminOrUp" class="flex">
<ui-btn to="/config" color="primary" class="w-52 mr-2">Configure Scanner</ui-btn> <ui-btn to="/config" color="primary" class="w-52 mr-2">Configure Scanner</ui-btn>
<ui-btn color="success" class="w-52" @click="scan">Scan Library</ui-btn> <ui-btn color="success" class="w-52" @click="scan">Scan Library</ui-btn>
</div> </div>
@ -44,8 +44,8 @@ export default {
} }
}, },
computed: { computed: {
isRootUser() { userIsAdminOrUp() {
return this.$store.getters['user/getIsRoot'] return this.$store.getters['user/getIsAdminOrUp']
}, },
showExperimentalFeatures() { showExperimentalFeatures() {
return this.$store.state.showExperimentalFeatures return this.$store.state.showExperimentalFeatures

View File

@ -25,11 +25,11 @@ export default {
return {} return {}
}, },
computed: { computed: {
userIsRoot() { userIsAdminOrUp() {
return this.$store.getters['user/getIsRoot'] return this.$store.getters['user/getIsAdminOrUp']
}, },
configRoutes() { configRoutes() {
if (!this.userIsRoot) { if (!this.userIsAdminOrUp) {
return [ return [
{ {
id: 'config-stats', id: 'config-stats',

View File

@ -6,9 +6,9 @@
</div> </div>
</template> </template>
<div v-if="initialized && !totalShelves && !hasFilter && isRootUser && entityName === 'books'" class="w-full flex flex-col items-center justify-center py-12"> <div v-if="initialized && !totalShelves && !hasFilter && entityName === 'books'" class="w-full flex flex-col items-center justify-center py-12">
<p class="text-center text-2xl font-book mb-4 py-4">{{ libraryName }} Library is empty!</p> <p class="text-center text-2xl font-book mb-4 py-4">{{ libraryName }} Library is empty!</p>
<div class="flex"> <div v-if="userIsAdminOrUp" class="flex">
<ui-btn to="/config" color="primary" class="w-52 mr-2">Configure Scanner</ui-btn> <ui-btn to="/config" color="primary" class="w-52 mr-2">Configure Scanner</ui-btn>
<ui-btn color="success" class="w-52" @click="scan">Scan Library</ui-btn> <ui-btn color="success" class="w-52" @click="scan">Scan Library</ui-btn>
</div> </div>
@ -79,8 +79,8 @@ export default {
} }
}, },
computed: { computed: {
isRootUser() { userIsAdminOrUp() {
return this.$store.getters['user/getIsRoot'] return this.$store.getters['user/getIsAdminOrUp']
}, },
showExperimentalFeatures() { showExperimentalFeatures() {
return this.$store.state.showExperimentalFeatures return this.$store.state.showExperimentalFeatures

View File

@ -343,8 +343,8 @@ export default {
userCanDownload() { userCanDownload() {
return this.store.getters['user/getUserCanDownload'] return this.store.getters['user/getUserCanDownload']
}, },
userIsRoot() { userIsAdminOrUp() {
return this.store.getters['user/getIsRoot'] return this.$store.getters['user/getIsAdminOrUp']
}, },
moreMenuItems() { moreMenuItems() {
if (this.recentEpisode) { if (this.recentEpisode) {
@ -383,7 +383,7 @@ export default {
text: 'Match' text: 'Match'
}) })
} }
if (this.userIsRoot && !this.isFile) { if (this.userIsAdminOrUp && !this.isFile) {
items.push({ items.push({
func: 'rescan', func: 'rescan',
text: 'Re-Scan' text: 'Re-Scan'

View File

@ -10,11 +10,11 @@
<div class="flex-grow" /> <div class="flex-grow" />
<ui-tooltip v-if="mediaType == 'book'" :disabled="!!quickMatching" :text="`(Root User Only) Populate empty book details & cover with first book result from '${libraryProvider}'. Does not overwrite details.`" direction="bottom" class="mr-4"> <ui-tooltip v-if="mediaType == 'book'" :disabled="!!quickMatching" :text="`(Root User Only) Populate empty book details & cover with first book result from '${libraryProvider}'. Does not overwrite details.`" direction="bottom" class="mr-4">
<ui-btn v-if="isRootUser" :loading="quickMatching" color="bg" type="button" class="h-full" small @click.stop.prevent="quickMatch">Quick Match</ui-btn> <ui-btn v-if="userIsAdminOrUp" :loading="quickMatching" color="bg" type="button" class="h-full" small @click.stop.prevent="quickMatch">Quick Match</ui-btn>
</ui-tooltip> </ui-tooltip>
<ui-tooltip :disabled="!!libraryScan" text="(Root User Only) Rescan audiobook including metadata" direction="bottom" class="mr-4"> <ui-tooltip :disabled="!!libraryScan" text="(Root User Only) Rescan audiobook including metadata" direction="bottom" class="mr-4">
<ui-btn v-if="isRootUser && !isFile" :loading="rescanning" :disabled="!!libraryScan" color="bg" type="button" class="h-full" small @click.stop.prevent="rescan">Re-Scan</ui-btn> <ui-btn v-if="userIsAdminOrUp && !isFile" :loading="rescanning" :disabled="!!libraryScan" color="bg" type="button" class="h-full" small @click.stop.prevent="rescan">Re-Scan</ui-btn>
</ui-tooltip> </ui-tooltip>
<ui-btn @click="submitForm">Submit</ui-btn> <ui-btn @click="submitForm">Submit</ui-btn>
@ -52,8 +52,8 @@ export default {
isFile() { isFile() {
return !!this.libraryItem && this.libraryItem.isFile return !!this.libraryItem && this.libraryItem.isFile
}, },
isRootUser() { userIsAdminOrUp() {
return this.$store.getters['user/getIsRoot'] return this.$store.getters['user/getIsAdminOrUp']
}, },
isMissing() { isMissing() {
return !!this.libraryItem && !!this.libraryItem.isMissing return !!this.libraryItem && !!this.libraryItem.isMissing

View File

@ -45,8 +45,8 @@
</td> </td>
<td class="py-0"> <td class="py-0">
<div class="w-full flex justify-center"> <div class="w-full flex justify-center">
<!-- <span class="material-icons hover:text-gray-400 cursor-pointer text-base pr-2" @click.stop="editUser(user)">edit</span> --> <!-- Dont show edit for non-root users -->
<div class="h-8 w-8 flex items-center justify-center text-white text-opacity-50 hover:text-opacity-100 cursor-pointer" @click.stop="editUser(user)"> <div v-if="user.type !== 'root' || userIsRoot" class="h-8 w-8 flex items-center justify-center text-white text-opacity-50 hover:text-opacity-100 cursor-pointer" @click.stop="editUser(user)">
<span class="material-icons text-base">edit</span> <span class="material-icons text-base">edit</span>
</div> </div>
<div v-show="user.type !== 'root'" class="h-8 w-8 flex items-center justify-center text-white text-opacity-50 hover:text-error cursor-pointer" @click.stop="deleteUserClick(user)"> <div v-show="user.type !== 'root'" class="h-8 w-8 flex items-center justify-center text-white text-opacity-50 hover:text-error cursor-pointer" @click.stop="deleteUserClick(user)">
@ -76,6 +76,9 @@ export default {
currentUserId() { currentUserId() {
return this.$store.state.user.user.id return this.$store.state.user.user.id
}, },
userIsRoot() {
return this.$store.getters['user/getIsRoot']
},
usersOnline() { usersOnline() {
var usermap = {} var usermap = {}
this.$store.state.users.users.forEach((u) => (usermap[u.id] = { online: true, session: u.session })) this.$store.state.users.users.forEach((u) => (usermap[u.id] = { online: true, session: u.session }))

View File

@ -134,9 +134,6 @@ export default {
showExperimentalFeatures() { showExperimentalFeatures() {
return this.$store.state.showExperimentalFeatures return this.$store.state.showExperimentalFeatures
}, },
isRootUser() {
return this.$store.getters['user/getIsRoot']
},
media() { media() {
return this.libraryItem.media || {} return this.libraryItem.media || {}
}, },

View File

@ -15,7 +15,7 @@
<script> <script>
export default { export default {
asyncData({ store, redirect, route }) { asyncData({ store, redirect, route }) {
if (!store.getters['user/getIsRoot']) { if (!store.getters['user/getIsAdminOrUp']) {
// Non-Root user only has access to the listening stats page // Non-Root user only has access to the listening stats page
if (route.name !== 'config-stats') { if (route.name !== 'config-stats') {
redirect('/config/stats') redirect('/config/stats')

View File

@ -34,11 +34,6 @@
<script> <script>
export default { export default {
asyncData({ store, redirect }) {
if (!store.getters['user/getIsRoot']) {
redirect('/?error=unauthorized')
}
},
data() { data() {
return { return {
search: null, search: null,

View File

@ -14,7 +14,7 @@
<h1 class="text-xl pl-2">{{ username }}</h1> <h1 class="text-xl pl-2">{{ username }}</h1>
</div> </div>
<div class="cursor-pointer text-gray-400 hover:text-white" @click="copyToClipboard(userToken)"> <div class="cursor-pointer text-gray-400 hover:text-white" @click="copyToClipboard(userToken)">
<p class="py-2 text-xs"> <p v-if="userToken" class="py-2 text-xs">
<strong class="text-white">API Token: </strong><br /><span class="text-white">{{ userToken }}</span <strong class="text-white">API Token: </strong><br /><span class="text-white">{{ userToken }}</span
><span class="material-icons pl-2 text-base">content_copy</span> ><span class="material-icons pl-2 text-base">content_copy</span>
</p> </p>

View File

@ -4,16 +4,16 @@ class BackupController {
constructor() { } constructor() { }
async create(req, res) { async create(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.error(`[BackupController] Non-Root user attempting to craete backup`, req.user) Logger.error(`[BackupController] Non-admin user attempting to craete backup`, req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }
this.backupManager.requestCreateBackup(res) this.backupManager.requestCreateBackup(res)
} }
async delete(req, res) { async delete(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.error(`[BackupController] Non-Root user attempting to delete backup`, req.user) Logger.error(`[BackupController] Non-admin user attempting to delete backup`, req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }
var backup = this.backupManager.backups.find(b => b.id === req.params.id) var backup = this.backupManager.backups.find(b => b.id === req.params.id)
@ -25,8 +25,8 @@ class BackupController {
} }
async upload(req, res) { async upload(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.error(`[BackupController] Non-Root user attempting to upload backup`, req.user) Logger.error(`[BackupController] Non-admin user attempting to upload backup`, req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }
if (!req.files.file) { if (!req.files.file) {
@ -37,8 +37,8 @@ class BackupController {
} }
async apply(req, res) { async apply(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.error(`[BackupController] Non-Root user attempting to apply backup`, req.user) Logger.error(`[BackupController] Non-admin user attempting to apply backup`, req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }
var backup = this.backupManager.backups.find(b => b.id === req.params.id) var backup = this.backupManager.backups.find(b => b.id === req.params.id)

View File

@ -320,7 +320,7 @@ class LibraryController {
// PATCH: Change the order of libraries // PATCH: Change the order of libraries
async reorder(req, res) { async reorder(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.error('[LibraryController] ReorderLibraries invalid user', req.user) Logger.error('[LibraryController] ReorderLibraries invalid user', req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }
@ -457,7 +457,7 @@ class LibraryController {
} }
async matchAll(req, res) { async matchAll(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.error(`[LibraryController] Non-root user attempted to match library items`, req.user) Logger.error(`[LibraryController] Non-root user attempted to match library items`, req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }
@ -467,7 +467,7 @@ class LibraryController {
// GET: api/scan (Root) // GET: api/scan (Root)
async scan(req, res) { async scan(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.error(`[LibraryController] Non-root user attempted to scan library`, req.user) Logger.error(`[LibraryController] Non-root user attempted to scan library`, req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }

View File

@ -331,8 +331,8 @@ class LibraryItemController {
// DELETE: api/items/all // DELETE: api/items/all
async deleteAll(req, res) { async deleteAll(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.warn('User other than root attempted to delete all library items', req.user) Logger.warn('User other than admin attempted to delete all library items', req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }
Logger.info('Removing all Library Items') Logger.info('Removing all Library Items')
@ -341,10 +341,10 @@ class LibraryItemController {
else res.sendStatus(500) else res.sendStatus(500)
} }
// GET: api/items/:id/scan (Root) // GET: api/items/:id/scan (admin)
async scan(req, res) { async scan(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.error(`[LibraryItemController] Non-root user attempted to scan library item`, req.user) Logger.error(`[LibraryItemController] Non-admin user attempted to scan library item`, req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }
@ -361,7 +361,7 @@ class LibraryItemController {
// POST: api/items/:id/audio-metadata // POST: api/items/:id/audio-metadata
async updateAudioFileMetadata(req, res) { async updateAudioFileMetadata(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.error(`[LibraryItemController] Non-root user attempted to update audio metadata`, req.user) Logger.error(`[LibraryItemController] Non-root user attempted to update audio metadata`, req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }

View File

@ -159,10 +159,10 @@ class MiscController {
res.json(downloads) res.json(downloads)
} }
// PATCH: api/settings (Root) // PATCH: api/settings (admin)
async updateServerSettings(req, res) { async updateServerSettings(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.error('User other than root attempting to update server settings', req.user) Logger.error('User other than admin attempting to update server settings', req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }
var settingsUpdate = req.body var settingsUpdate = req.body
@ -185,9 +185,9 @@ class MiscController {
}) })
} }
// POST: api/purgecache (Root) // POST: api/purgecache (admin)
async purgeCache(req, res) { async purgeCache(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
return res.sendStatus(403) return res.sendStatus(403)
} }
Logger.info(`[ApiRouter] Purging all cache`) Logger.info(`[ApiRouter] Purging all cache`)
@ -239,8 +239,8 @@ class MiscController {
} }
getAllTags(req, res) { getAllTags(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.error(`[MiscController] Non-root user attempted to getAllTags`) Logger.error(`[MiscController] Non-admin user attempted to getAllTags`)
return res.sendStatus(404) return res.sendStatus(404)
} }
var tags = [] var tags = []

View File

@ -7,14 +7,15 @@ class UserController {
constructor() { } constructor() { }
findAll(req, res) { findAll(req, res) {
if (!req.user.isRoot) return res.sendStatus(403) if (!req.user.isAdminOrUp) return res.sendStatus(403)
var users = this.db.users.map(u => this.userJsonWithItemProgressDetails(u)) const hideRootToken = !req.user.isRoot
var users = this.db.users.map(u => this.userJsonWithItemProgressDetails(u, hideRootToken))
res.json(users) res.json(users)
} }
findOne(req, res) { findOne(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.error('User other than root attempting to get user', req.user) Logger.error('User other than admin attempting to get user', req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }
@ -23,12 +24,12 @@ class UserController {
return res.sendStatus(404) return res.sendStatus(404)
} }
res.json(this.userJsonWithItemProgressDetails(user)) res.json(this.userJsonWithItemProgressDetails(user, !req.user.isRoot))
} }
async create(req, res) { async create(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.warn('Non-root user attempted to create user', req.user) Logger.warn('Non-admin user attempted to create user', req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }
var account = req.body var account = req.body
@ -57,8 +58,8 @@ class UserController {
} }
async update(req, res) { async update(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.error('User other than root attempting to update user', req.user) Logger.error('[UserController] User other than admin attempting to update user', req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }
@ -67,6 +68,11 @@ class UserController {
return res.sendStatus(404) return res.sendStatus(404)
} }
if (user.type === 'root' && !req.user.isRoot) {
Logger.error(`[UserController] Admin user attempted to update root user`, req.user.username)
return res.sendStatus(403)
}
var account = req.body var account = req.body
if (account.username !== undefined && account.username !== user.username) { if (account.username !== undefined && account.username !== user.username) {
@ -95,8 +101,8 @@ class UserController {
} }
async delete(req, res) { async delete(req, res) {
if (!req.user.isRoot) { if (!req.user.isAdminOrUp) {
Logger.error('User other than root attempting to delete user', req.user) Logger.error('User other than admin attempting to delete user', req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }
if (req.params.id === 'root') { if (req.params.id === 'root') {
@ -133,7 +139,7 @@ class UserController {
// GET: api/users/:id/listening-sessions // GET: api/users/:id/listening-sessions
async getListeningSessions(req, res) { async getListeningSessions(req, res) {
if (!req.user.isRoot && req.user.id !== req.params.id) { if (!req.user.isAdminOrUp && req.user.id !== req.params.id) {
return res.sendStatus(403) return res.sendStatus(403)
} }
var listeningSessions = await this.getUserListeningSessionsHelper(req.params.id) var listeningSessions = await this.getUserListeningSessionsHelper(req.params.id)
@ -142,7 +148,7 @@ class UserController {
// GET: api/users/:id/listening-stats // GET: api/users/:id/listening-stats
async getListeningStats(req, res) { async getListeningStats(req, res) {
if (!req.user.isRoot && req.user.id !== req.params.id) { if (!req.user.isAdminOrUp && req.user.id !== req.params.id) {
return res.sendStatus(403) return res.sendStatus(403)
} }
var listeningStats = await this.getUserListeningStatsHelpers(req.params.id) var listeningStats = await this.getUserListeningStatsHelpers(req.params.id)

View File

@ -239,8 +239,11 @@ class ApiRouter {
// //
// Helper Methods // Helper Methods
// //
userJsonWithItemProgressDetails(user) { userJsonWithItemProgressDetails(user, hideRootToken = false) {
var json = user.toJSONForBrowser() var json = user.toJSONForBrowser()
if (json.type === 'root' && hideRootToken) {
json.token = ''
}
json.mediaProgress = json.mediaProgress.map(lip => { json.mediaProgress = json.mediaProgress.map(lip => {
var libraryItem = this.db.libraryItems.find(li => li.id === lip.id) var libraryItem = this.db.libraryItems.find(li => li.id === lip.id)