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 @@
+ +
+
+

Can Access All Tags

+
+
+ +
+
+ +
+ +
@@ -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))