mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-26 00:14:49 +01:00
Add:Restrict user permissions by tag
This commit is contained in:
parent
f8d0384155
commit
27f1bd90f9
@ -77,6 +77,19 @@
|
|||||||
<div v-if="!newUser.permissions.accessAllLibraries" class="my-4">
|
<div v-if="!newUser.permissions.accessAllLibraries" class="my-4">
|
||||||
<ui-multi-select-dropdown v-model="newUser.librariesAccessible" :items="libraryItems" label="Libraries Accessible to User" />
|
<ui-multi-select-dropdown v-model="newUser.librariesAccessible" :items="libraryItems" label="Libraries Accessible to User" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-cen~ter my-2 max-w-md">
|
||||||
|
<div class="w-1/2">
|
||||||
|
<p>Can Access All Tags</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-1/2">
|
||||||
|
<ui-toggle-switch v-model="newUser.permissions.accessAllTags" @input="accessAllTagsToggled" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!newUser.permissions.accessAllTags" class="my-4">
|
||||||
|
<ui-multi-select-dropdown v-model="newUser.itemsTagsAccessible" :items="itemTags" label="Tags Accessible to User" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex pt-4">
|
<div class="flex pt-4">
|
||||||
@ -103,7 +116,9 @@ export default {
|
|||||||
processing: false,
|
processing: false,
|
||||||
newUser: {},
|
newUser: {},
|
||||||
isNew: true,
|
isNew: true,
|
||||||
accountTypes: ['guest', 'user', 'admin']
|
accountTypes: ['guest', 'user', 'admin'],
|
||||||
|
tags: [],
|
||||||
|
loadingTags: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -135,9 +150,40 @@ export default {
|
|||||||
},
|
},
|
||||||
libraryItems() {
|
libraryItems() {
|
||||||
return this.libraries.map((lib) => ({ text: lib.name, value: lib.id }))
|
return this.libraries.map((lib) => ({ text: lib.name, value: lib.id }))
|
||||||
|
},
|
||||||
|
itemTags() {
|
||||||
|
return this.tags.map((t) => {
|
||||||
|
return {
|
||||||
|
text: t,
|
||||||
|
value: t
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
accessAllTagsToggled(val) {
|
||||||
|
if (!val) {
|
||||||
|
this.fetchAllTags()
|
||||||
|
}
|
||||||
|
if (!val && !this.newUser.itemTagsAccessible.length) {
|
||||||
|
this.newUser.itemTagsAccessible = this.libraries.map((l) => l.id)
|
||||||
|
} else if (val && this.newUser.itemTagsAccessible.length) {
|
||||||
|
this.newUser.itemTagsAccessible = []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchAllTags() {
|
||||||
|
this.loadingTags = true
|
||||||
|
this.$axios
|
||||||
|
.$get(`/api/tags`)
|
||||||
|
.then((tags) => {
|
||||||
|
this.tags = tags
|
||||||
|
this.loadingTags = false
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to load tags', error)
|
||||||
|
this.loadingTags = false
|
||||||
|
})
|
||||||
|
},
|
||||||
accessAllLibrariesToggled(val) {
|
accessAllLibrariesToggled(val) {
|
||||||
if (!val && !this.newUser.librariesAccessible.length) {
|
if (!val && !this.newUser.librariesAccessible.length) {
|
||||||
this.newUser.librariesAccessible = this.libraries.map((l) => l.id)
|
this.newUser.librariesAccessible = this.libraries.map((l) => l.id)
|
||||||
@ -223,20 +269,22 @@ export default {
|
|||||||
download: type !== 'guest',
|
download: type !== 'guest',
|
||||||
update: type === 'admin',
|
update: type === 'admin',
|
||||||
delete: type === 'admin',
|
delete: type === 'admin',
|
||||||
upload: type === 'admin'
|
upload: type === 'admin',
|
||||||
|
accessAllLibraries: true,
|
||||||
|
accessAllTags: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
this.isNew = !this.account
|
this.isNew = !this.account
|
||||||
if (this.account) {
|
if (this.account) {
|
||||||
var librariesAccessible = this.account.librariesAccessible || []
|
|
||||||
this.newUser = {
|
this.newUser = {
|
||||||
username: this.account.username,
|
username: this.account.username,
|
||||||
password: this.account.password,
|
password: this.account.password,
|
||||||
type: this.account.type,
|
type: this.account.type,
|
||||||
isActive: this.account.isActive,
|
isActive: this.account.isActive,
|
||||||
permissions: { ...this.account.permissions },
|
permissions: { ...this.account.permissions },
|
||||||
librariesAccessible: [...librariesAccessible]
|
librariesAccessible: [...(this.account.librariesAccessible || [])],
|
||||||
|
itemTagsAccessible: [...(this.account.itemTagsAccessible || [])]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.newUser = {
|
this.newUser = {
|
||||||
@ -249,7 +297,8 @@ export default {
|
|||||||
update: false,
|
update: false,
|
||||||
delete: false,
|
delete: false,
|
||||||
upload: false,
|
upload: false,
|
||||||
accessAllLibraries: true
|
accessAllLibraries: true,
|
||||||
|
accessAllTags: true
|
||||||
},
|
},
|
||||||
librariesAccessible: []
|
librariesAccessible: []
|
||||||
}
|
}
|
||||||
|
@ -233,7 +233,9 @@ class LibraryController {
|
|||||||
if (!req.params.series) {
|
if (!req.params.series) {
|
||||||
return res.status(403).send('Invalid series')
|
return res.status(403).send('Invalid series')
|
||||||
}
|
}
|
||||||
var libraryItems = this.db.libraryItems.filter(li => li.libraryId === req.library.id && li.book.series === req.params.series)
|
var libraryItems = this.db.libraryItems.filter(li => {
|
||||||
|
return li.libraryId === req.library.id && li.book.series === req.params.series
|
||||||
|
})
|
||||||
if (!libraryItems.length) {
|
if (!libraryItems.length) {
|
||||||
return res.status(404).send('Series not found')
|
return res.status(404).send('Series not found')
|
||||||
}
|
}
|
||||||
@ -530,7 +532,9 @@ class LibraryController {
|
|||||||
return res.status(404).send('Library not found')
|
return res.status(404).send('Library not found')
|
||||||
}
|
}
|
||||||
req.library = library
|
req.library = library
|
||||||
req.libraryItems = this.db.libraryItems.filter(li => li.libraryId === library.id)
|
req.libraryItems = this.db.libraryItems.filter(li => {
|
||||||
|
return li.libraryId === library.id && req.user.checkCanAccessLibraryItemWithTags(li.media.tags)
|
||||||
|
})
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,6 +262,11 @@ class LibraryItemController {
|
|||||||
return res.sendStatus(403)
|
return res.sendStatus(403)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check user can access this library item
|
||||||
|
if (!req.user.checkCanAccessLibraryItemWithTags(item.media.tags)) {
|
||||||
|
return res.sendStatus(403)
|
||||||
|
}
|
||||||
|
|
||||||
if (req.method == 'DELETE' && !req.user.canDelete) {
|
if (req.method == 'DELETE' && !req.user.canDelete) {
|
||||||
Logger.warn(`[LibraryItemController] User attempted to delete without permission`, req.user)
|
Logger.warn(`[LibraryItemController] User attempted to delete without permission`, req.user)
|
||||||
return res.sendStatus(403)
|
return res.sendStatus(403)
|
||||||
|
@ -170,5 +170,21 @@ class MiscController {
|
|||||||
}
|
}
|
||||||
res.json({ user: req.user })
|
res.json({ user: req.user })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAllTags(req, res) {
|
||||||
|
if (!req.user.isRoot) {
|
||||||
|
Logger.error(`[MiscController] Non-root user attempted to getAllTags`)
|
||||||
|
return res.sendStatus(404)
|
||||||
|
}
|
||||||
|
var tags = []
|
||||||
|
this.db.libraryItems.forEach((li) => {
|
||||||
|
if (li.media.tags && li.media.tags.length) {
|
||||||
|
li.media.tags.forEach((tag) => {
|
||||||
|
if (!tags.includes(tag)) tags.push(tag)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
res.json(tags)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module.exports = new MiscController()
|
module.exports = new MiscController()
|
@ -20,6 +20,7 @@ class User {
|
|||||||
this.settings = {}
|
this.settings = {}
|
||||||
this.permissions = {}
|
this.permissions = {}
|
||||||
this.librariesAccessible = [] // Library IDs (Empty if ALL libraries)
|
this.librariesAccessible = [] // Library IDs (Empty if ALL libraries)
|
||||||
|
this.itemTagsAccessible = [] // Empty if ALL item tags accessible
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
this.construct(user)
|
this.construct(user)
|
||||||
@ -44,6 +45,9 @@ class User {
|
|||||||
get canAccessAllLibraries() {
|
get canAccessAllLibraries() {
|
||||||
return !!this.permissions.accessAllLibraries && this.isActive
|
return !!this.permissions.accessAllLibraries && this.isActive
|
||||||
}
|
}
|
||||||
|
get canAccessAllTags() {
|
||||||
|
return !!this.permissions.accessAllTags && this.isActive
|
||||||
|
}
|
||||||
get hasPw() {
|
get hasPw() {
|
||||||
return !!this.pash && !!this.pash.length
|
return !!this.pash && !!this.pash.length
|
||||||
}
|
}
|
||||||
@ -68,7 +72,8 @@ class User {
|
|||||||
update: true,
|
update: true,
|
||||||
delete: this.type === 'root',
|
delete: this.type === 'root',
|
||||||
upload: this.type === 'root' || this.type === 'admin',
|
upload: this.type === 'root' || this.type === 'admin',
|
||||||
accessAllLibraries: true
|
accessAllLibraries: true,
|
||||||
|
accessAllTags: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +92,8 @@ class User {
|
|||||||
createdAt: this.createdAt,
|
createdAt: this.createdAt,
|
||||||
settings: this.settings,
|
settings: this.settings,
|
||||||
permissions: this.permissions,
|
permissions: this.permissions,
|
||||||
librariesAccessible: [...this.librariesAccessible]
|
librariesAccessible: [...this.librariesAccessible],
|
||||||
|
itemTagsAccessible: [...this.itemTagsAccessible]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +111,8 @@ class User {
|
|||||||
createdAt: this.createdAt,
|
createdAt: this.createdAt,
|
||||||
settings: this.settings,
|
settings: this.settings,
|
||||||
permissions: this.permissions,
|
permissions: this.permissions,
|
||||||
librariesAccessible: [...this.librariesAccessible]
|
librariesAccessible: [...this.librariesAccessible],
|
||||||
|
itemTagsAccessible: [...this.itemTagsAccessible]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,8 +158,11 @@ class User {
|
|||||||
|
|
||||||
// Library restriction permissions added v1.4.14, defaults to all libraries
|
// Library restriction permissions added v1.4.14, defaults to all libraries
|
||||||
if (this.permissions.accessAllLibraries === undefined) this.permissions.accessAllLibraries = true
|
if (this.permissions.accessAllLibraries === undefined) this.permissions.accessAllLibraries = true
|
||||||
|
// Library restriction permissions added v2.0, defaults to all libraries
|
||||||
|
if (this.permissions.accessAllTags === undefined) this.permissions.accessAllTags = true
|
||||||
|
|
||||||
this.librariesAccessible = (user.librariesAccessible || []).map(l => l)
|
this.librariesAccessible = [...(user.librariesAccessible || [])]
|
||||||
|
this.itemTagsAccessible = [...(user.itemTagsAccessible || [])]
|
||||||
}
|
}
|
||||||
|
|
||||||
update(payload) {
|
update(payload) {
|
||||||
@ -190,6 +200,19 @@ class User {
|
|||||||
this.librariesAccessible = []
|
this.librariesAccessible = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update accessible libraries
|
||||||
|
if (payload.itemTagsAccessible !== undefined) {
|
||||||
|
if (payload.itemTagsAccessible.length) {
|
||||||
|
if (payload.itemTagsAccessible.join(',') !== this.itemTagsAccessible.join(',')) {
|
||||||
|
hasUpdates = true
|
||||||
|
this.itemTagsAccessible = [...payload.itemTagsAccessible]
|
||||||
|
}
|
||||||
|
} else if (this.itemTagsAccessible.length > 0) {
|
||||||
|
hasUpdates = true
|
||||||
|
this.itemTagsAccessible = []
|
||||||
|
}
|
||||||
|
}
|
||||||
return hasUpdates
|
return hasUpdates
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,6 +293,11 @@ class User {
|
|||||||
return this.librariesAccessible.includes(libraryId)
|
return this.librariesAccessible.includes(libraryId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkCanAccessLibraryItemWithTags(tags) {
|
||||||
|
if (this.permissions.accessAllTags || !tags || !tags.length) return true
|
||||||
|
return this.itemTagsAccessible.some(tag => tags.includes(tag))
|
||||||
|
}
|
||||||
|
|
||||||
findBookmark(libraryItemId, time) {
|
findBookmark(libraryItemId, time) {
|
||||||
return this.bookmarks.find(bm => bm.libraryItemId === libraryItemId && bm.time == time)
|
return this.bookmarks.find(bm => bm.libraryItemId === libraryItemId && bm.time == time)
|
||||||
}
|
}
|
||||||
|
@ -192,6 +192,7 @@ class ApiRouter {
|
|||||||
this.router.get('/search/books', MiscController.findBooks.bind(this))
|
this.router.get('/search/books', MiscController.findBooks.bind(this))
|
||||||
this.router.get('/search/podcast', MiscController.findPodcasts.bind(this))
|
this.router.get('/search/podcast', MiscController.findPodcasts.bind(this))
|
||||||
this.router.get('/search/authors', MiscController.findAuthor.bind(this))
|
this.router.get('/search/authors', MiscController.findAuthor.bind(this))
|
||||||
|
this.router.get('/tags', MiscController.getAllTags.bind(this))
|
||||||
|
|
||||||
// OLD
|
// OLD
|
||||||
// this.router.post('/syncUserAudiobookData', this.syncUserAudiobookData.bind(this))
|
// this.router.post('/syncUserAudiobookData', this.syncUserAudiobookData.bind(this))
|
||||||
|
Loading…
Reference in New Issue
Block a user