diff --git a/client/components/modals/AccountModal.vue b/client/components/modals/AccountModal.vue
index 2a25eac2..5a020706 100644
--- a/client/components/modals/AccountModal.vue
+++ b/client/components/modals/AccountModal.vue
@@ -77,6 +77,19 @@
+
+
+
+
+
+
@@ -103,7 +116,9 @@ export default {
processing: false,
newUser: {},
isNew: true,
- accountTypes: ['guest', 'user', 'admin']
+ accountTypes: ['guest', 'user', 'admin'],
+ tags: [],
+ loadingTags: false
}
},
watch: {
@@ -135,9 +150,40 @@ export default {
},
libraryItems() {
return this.libraries.map((lib) => ({ text: lib.name, value: lib.id }))
+ },
+ itemTags() {
+ return this.tags.map((t) => {
+ return {
+ text: t,
+ value: t
+ }
+ })
}
},
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) {
if (!val && !this.newUser.librariesAccessible.length) {
this.newUser.librariesAccessible = this.libraries.map((l) => l.id)
@@ -223,20 +269,22 @@ export default {
download: type !== 'guest',
update: type === 'admin',
delete: type === 'admin',
- upload: type === 'admin'
+ upload: type === 'admin',
+ accessAllLibraries: true,
+ accessAllTags: true
}
},
init() {
this.isNew = !this.account
if (this.account) {
- var librariesAccessible = this.account.librariesAccessible || []
this.newUser = {
username: this.account.username,
password: this.account.password,
type: this.account.type,
isActive: this.account.isActive,
permissions: { ...this.account.permissions },
- librariesAccessible: [...librariesAccessible]
+ librariesAccessible: [...(this.account.librariesAccessible || [])],
+ itemTagsAccessible: [...(this.account.itemTagsAccessible || [])]
}
} else {
this.newUser = {
@@ -249,7 +297,8 @@ export default {
update: false,
delete: false,
upload: false,
- accessAllLibraries: true
+ accessAllLibraries: true,
+ accessAllTags: true
},
librariesAccessible: []
}
diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js
index 1d5bf315..09708619 100644
--- a/server/controllers/LibraryController.js
+++ b/server/controllers/LibraryController.js
@@ -233,7 +233,9 @@ class LibraryController {
if (!req.params.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) {
return res.status(404).send('Series not found')
}
@@ -530,7 +532,9 @@ class LibraryController {
return res.status(404).send('Library not found')
}
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()
}
}
diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js
index d5ef371b..9b015224 100644
--- a/server/controllers/LibraryItemController.js
+++ b/server/controllers/LibraryItemController.js
@@ -262,6 +262,11 @@ class LibraryItemController {
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) {
Logger.warn(`[LibraryItemController] User attempted to delete without permission`, req.user)
return res.sendStatus(403)
diff --git a/server/controllers/MiscController.js b/server/controllers/MiscController.js
index fe11c03a..df1219ed 100644
--- a/server/controllers/MiscController.js
+++ b/server/controllers/MiscController.js
@@ -170,5 +170,21 @@ class MiscController {
}
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()
\ No newline at end of file
diff --git a/server/objects/user/User.js b/server/objects/user/User.js
index 1d04dda4..1457a909 100644
--- a/server/objects/user/User.js
+++ b/server/objects/user/User.js
@@ -20,6 +20,7 @@ class User {
this.settings = {}
this.permissions = {}
this.librariesAccessible = [] // Library IDs (Empty if ALL libraries)
+ this.itemTagsAccessible = [] // Empty if ALL item tags accessible
if (user) {
this.construct(user)
@@ -44,6 +45,9 @@ class User {
get canAccessAllLibraries() {
return !!this.permissions.accessAllLibraries && this.isActive
}
+ get canAccessAllTags() {
+ return !!this.permissions.accessAllTags && this.isActive
+ }
get hasPw() {
return !!this.pash && !!this.pash.length
}
@@ -68,7 +72,8 @@ class User {
update: true,
delete: this.type === 'root',
upload: this.type === 'root' || this.type === 'admin',
- accessAllLibraries: true
+ accessAllLibraries: true,
+ accessAllTags: true
}
}
@@ -87,7 +92,8 @@ class User {
createdAt: this.createdAt,
settings: this.settings,
permissions: this.permissions,
- librariesAccessible: [...this.librariesAccessible]
+ librariesAccessible: [...this.librariesAccessible],
+ itemTagsAccessible: [...this.itemTagsAccessible]
}
}
@@ -105,7 +111,8 @@ class User {
createdAt: this.createdAt,
settings: this.settings,
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
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) {
@@ -190,6 +200,19 @@ class User {
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
}
@@ -270,6 +293,11 @@ class User {
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) {
return this.bookmarks.find(bm => bm.libraryItemId === libraryItemId && bm.time == time)
}
diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js
index b3defb2c..d3f1a71d 100644
--- a/server/routers/ApiRouter.js
+++ b/server/routers/ApiRouter.js
@@ -192,6 +192,7 @@ class ApiRouter {
this.router.get('/search/books', MiscController.findBooks.bind(this))
this.router.get('/search/podcast', MiscController.findPodcasts.bind(this))
this.router.get('/search/authors', MiscController.findAuthor.bind(this))
+ this.router.get('/tags', MiscController.getAllTags.bind(this))
// OLD
// this.router.post('/syncUserAudiobookData', this.syncUserAudiobookData.bind(this))