Browse Source

data, logs, logs-content, story, story-content: Added permission check in UI to hide disallowed actions.

1.0
Gisle Aune 6 years ago
parent
commit
8345d1fc91
  1. 24
      marko/components/if-permitted/component.js
  2. 3
      marko/components/if-permitted/index.marko
  3. 6
      marko/components/menu/index.marko
  4. 12
      marko/page/data/components/add-character-modal/index.marko
  5. 2
      marko/page/data/components/channel-list/index.marko
  6. 4
      marko/page/data/components/channel/index.marko
  7. 2
      marko/page/data/components/channels-page/index.marko
  8. 1
      marko/page/data/components/character-list/index.marko
  9. 8
      marko/page/data/components/character/index.marko
  10. 2
      marko/page/data/components/characters-page/index.marko
  11. 12
      marko/page/data/components/data-menu/index.marko
  12. 9
      marko/page/logs-content/components/logs-content-menu/index.marko
  13. 9
      marko/page/logs-content/components/page/index.marko
  14. 14
      marko/page/logs-content/components/post/index.marko
  15. 13
      marko/page/logs/components/logs-menu/index.marko
  16. 8
      marko/page/story-content/components/chapter/index.marko
  17. 14
      marko/page/story-content/components/page/index.marko
  18. 11
      marko/page/story-content/components/story-content-menu/index.marko
  19. 4
      marko/page/story-content/components/story-tags/index.marko
  20. 2
      marko/page/story-content/components/story-tags/style.less
  21. 4
      marko/page/story/components/story-menu/index.marko
  22. 35
      middleware/locals.js

24
marko/components/if-permitted/component.js

@ -0,0 +1,24 @@
module.exports = class {
onCreate() {
this.state = {
permitted: false
}
}
onInput(input) {
console.log(input.user, input.permission)
if (!input.user || !input.user.loggedIn) {
this.state.permitted = false
return
}
if (input.author != null && input.author != "" && input.user.name === input.author) {
this.state.permitted = input.user.permissions.includes("member")
} else if (Array.isArray(input.permission)) {
this.state.permitted = input.user.permissions.find(p => input.permission.includes(p))
} else {
this.state.permitted = input.user.permissions.includes(input.permission)
}
}
}

3
marko/components/if-permitted/index.marko

@ -0,0 +1,3 @@
<if(state.permitted)>
<include(input.renderBody) />
</if>

6
marko/components/menu/index.marko

@ -1,8 +1,10 @@
<div class="menu"> <div class="menu">
<include(input.renderBody) />
<div class="menu-body">
<include(input.renderBody) />
</div>
<if(input.user != null)> <if(input.user != null)>
<menu-gap /> <menu-gap />
<menu-header>User</menu-header>
<menu-header key="user">User</menu-header>
<menu-link if(!input.user.loggedIn) icon="L" href="/auth/login" on-click("loginClicked")>Login</menu-link> <menu-link if(!input.user.loggedIn) icon="L" href="/auth/login" on-click("loginClicked")>Login</menu-link>
<menu-link if(input.user.loggedIn) icon="L" href="/auth/logout" on-click("logoutClicked")>Logout <span class="weak">(${input.user.name})</span></menu-link> <menu-link if(input.user.loggedIn) icon="L" href="/auth/logout" on-click("logoutClicked")>Logout <span class="weak">(${input.user.name})</span></menu-link>
</if> </if>

12
marko/page/data/components/add-character-modal/index.marko

@ -6,14 +6,16 @@
<label>Full Name</label> <label>Full Name</label>
<input key="name" placeholder="" class="big" on-change("change", "name") value=state.values.name /> <input key="name" placeholder="" class="big" on-change("change", "name") value=state.values.name />
<label>IRC Nick</label>
<input key="nick" placeholder="" on-change("change", "nick") value=state.values.nick />
<label>Short Name</label> <label>Short Name</label>
<input key="shortName" placeholder="(Optional)" on-change("change", "shortName") value=state.values.shortName /> <input key="shortName" placeholder="(Optional)" on-change("change", "shortName") value=state.values.shortName />
<label>IRC Nick</label>
<input key="nick" placeholder="" class="big" on-change("change", "nick") value=state.values.nick />
<label>Author</label>
<input key="author" placeholder=(input.user.name) on-change("change", "author") value=state.values.author />
<if-permitted user=input.user permission="character.add">
<label>Author</label>
<input key="author" placeholder=(input.user.name) on-change("change", "author") value=state.values.author />
</if-permitted>
<label>Description</label> <label>Description</label>
<textarea key="description" placeholder="Description" on-change("change", "description") value=state.values.description /> <textarea key="description" placeholder="Description" on-change("change", "description") value=state.values.description />

2
marko/page/data/components/channel-list/index.marko

@ -1,5 +1,5 @@
<compact-list> <compact-list>
<compact-list-item for(channel in input.channels) key=(channel.name.replace("'", "_APOS_"))> <compact-list-item for(channel in input.channels) key=(channel.name.replace("'", "_APOS_"))>
<channel data=channel on-edited("emit", "edited", channel)/>
<channel data=channel user=input.user on-edited("emit", "edited", channel)/>
</compact-list-item> </compact-list-item>
</compact-list> </compact-list>

4
marko/page/data/components/channel/index.marko

@ -4,7 +4,9 @@
<compact-list-property long label="Location" weak=(input.data.locationName === "") value=(input.data.locationName || "None") /> <compact-list-property long label="Location" weak=(input.data.locationName === "") value=(input.data.locationName || "None") />
<compact-list-property short weak=!input.data.logged short label="Logged" value=(input.data.logged ? "On" : "Off") /> <compact-list-property short weak=!input.data.logged short label="Logged" value=(input.data.logged ? "On" : "Off") />
<compact-list-options> <compact-list-options>
<a on-click("open", "edit")>Edit</a>
<if-permitted user=input.user permission="channel.edit">
<a on-click("open", "edit")>Edit</a>
</if-permitted>
</compact-list-options> </compact-list-options>
<edit-channel-modal enabled=(state.modal === "edit") channel=input.data on-edited("emit", "edited") on-close("close") /> <edit-channel-modal enabled=(state.modal === "edit") channel=input.data on-edited("emit", "edited") on-close("close") />
</if> </if>

2
marko/page/data/components/channels-page/index.marko

@ -1,6 +1,6 @@
<data-menu categories=input.categories selected=(input.selected || {}) user=input.user on-open("open") /> <data-menu categories=input.categories selected=(input.selected || {}) user=input.user on-open("open") />
<main> <main>
<channel-list channels=state.channels on-edited("channelEdited") />
<channel-list channels=state.channels user=input.user on-edited("channelEdited") />
</main> </main>
<add-channel-modal enabled=(state.modal === "channel.add") user=input.user on-added("channelAdded") on-close("close") /> <add-channel-modal enabled=(state.modal === "channel.add") user=input.user on-added("channelAdded") on-close("close") />
<add-character-modal enabled=(state.modal === "character.add") user=input.user on-close("close") /> <add-character-modal enabled=(state.modal === "character.add") user=input.user on-close("close") />

1
marko/page/data/components/character-list/index.marko

@ -1,6 +1,7 @@
<compact-list> <compact-list>
<compact-list-item for(character in input.characters) key=character.id id=character.id> <compact-list-item for(character in input.characters) key=character.id id=character.id>
<character data=character <character data=character
user=input.user
on-removed("emit", "removed", character) on-removed("emit", "removed", character)
on-edited("emit", "edited", character) on-edited("emit", "edited", character)
on-nicks("emit", "nicks", character) on-nicks("emit", "nicks", character)

8
marko/page/data/components/character/index.marko

@ -4,9 +4,13 @@
<compact-list-property long label="Name" value=input.data.name /> <compact-list-property long label="Name" value=input.data.name />
<compact-list-property short label="Author" value=input.data.author /> <compact-list-property short label="Author" value=input.data.author />
<compact-list-options long> <compact-list-options long>
<a key="edit" on-click("open", "edit", input.data)>Edit</a>
<a key="nicks" on-click("open", "nicks", input.data)>Nicks</a>
<if-permitted user=input.user author=input.data.author permission="character.edit">
<a key="edit" on-click("open", "edit", input.data)>Edit</a>
<a key="nicks" on-click("open", "nicks", input.data)>Nicks</a>
</if-permitted>
<if-permitted user=input.user author=input.data.author permission="character.remove">
<a key="remove" on-click("open", "remove", input.data)>Remove</a> <a key="remove" on-click("open", "remove", input.data)>Remove</a>
</if-permitted>
</compact-list-options> </compact-list-options>
<remove-character-modal enabled=(state.modal === "remove") character=input.data on-removed("emit", "removed") on-close("close") /> <remove-character-modal enabled=(state.modal === "remove") character=input.data on-removed("emit", "removed") on-close("close") />
<edit-character-modal enabled=(state.modal === "edit") character=input.data on-edited("emit", "edited") on-close("close") /> <edit-character-modal enabled=(state.modal === "edit") character=input.data on-edited("emit", "edited") on-close("close") />

2
marko/page/data/components/characters-page/index.marko

@ -1,6 +1,6 @@
<data-menu categories=input.categories selected=(input.selected || {}) user=input.user on-open("open") /> <data-menu categories=input.categories selected=(input.selected || {}) user=input.user on-open("open") />
<main> <main>
<character-list characters=state.characters on-removed("characterRemoved") on-edited("characterEdited") on-nicks("characterNicksChanged") />
<character-list user=input.user characters=state.characters on-removed("characterRemoved") on-edited("characterEdited") on-nicks("characterNicksChanged") />
</main> </main>
<add-character-modal enabled=(state.modal === "character.add") user=input.user on-added("characterAdded") on-close("close") /> <add-character-modal enabled=(state.modal === "character.add") user=input.user on-added("characterAdded") on-close("close") />
<add-channel-modal enabled=(state.modal === "channel.add") user=input.user on-close("close") /> <add-channel-modal enabled=(state.modal === "channel.add") user=input.user on-close("close") />

12
marko/page/data/components/data-menu/index.marko

@ -3,7 +3,13 @@
<menu-link key="characters" selected=input.selected.characters icon="C" href="/data/characters/">Characters</menu-link> <menu-link key="characters" selected=input.selected.characters icon="C" href="/data/characters/">Characters</menu-link>
<menu-link key="channels" selected=input.selected.channels icon="#" href="/data/channels/">Channels</menu-link> <menu-link key="channels" selected=input.selected.channels icon="#" href="/data/channels/">Channels</menu-link>
<menu-gap /> <menu-gap />
<menu-header>Add</menu-header>
<menu-link dark key="characters_add" icon="+" on-click("emit", "open", "character.add")>Character</menu-link>
<menu-link dark key="channels_add" icon="+" on-click("emit", "open", "channel.add")>Channel</menu-link>
<if-permitted user=input.user permission=["member", "character.add", "channel.add"]>
<menu-header>Add</menu-header>
<if-permitted user=input.user permission=["character.add", "member"]>
<menu-link dark key="characters_add" icon="+" on-click("emit", "open", "character.add")>Character</menu-link>
</if-permitted>
<if-permitted user=input.user permission="channel.add">
<menu-link dark key="channels_add" icon="+" on-click("emit", "open", "channel.add")>Channel</menu-link>
</if-permitted>
</if-permitted>
</menu> </menu>

9
marko/page/logs-content/components/logs-content-menu/index.marko

@ -1,8 +1,9 @@
<menu user=input.user> <menu user=input.user>
<menu-header>Log</menu-header>
<menu-link dark key="_create" on-click("select", "post.add", null) icon="+">Add Post</menu-link>
<menu-gap />
<if-permitted user=input.user permission="post.add">
<menu-header>Log</menu-header>
<menu-link dark key="_create" on-click("select", "post.add", null) icon="+">Add Post</menu-link>
<menu-gap />
</if-permitted>
<menu-header>Links</menu-header> <menu-header>Links</menu-header>
<menu-link href=("/logs/?channels="+input.log.channel.name) icon="#">${input.log.channel.name}</menu-link> <menu-link href=("/logs/?channels="+input.log.channel.name) icon="#">${input.log.channel.name}</menu-link>
<menu-link if(input.log.channel.eventName) href=("/logs/?channels="+input.log.channel.eventName) icon="E">${input.log.channel.eventName}</menu-link> <menu-link if(input.log.channel.eventName) href=("/logs/?channels="+input.log.channel.eventName) icon="E">${input.log.channel.eventName}</menu-link>

9
marko/page/logs-content/components/page/index.marko

@ -3,8 +3,12 @@
<div class="logs-content"> <div class="logs-content">
<div class="header"> <div class="header">
<h1 class="color-primary">${component.title}</h1> <h1 class="color-primary">${component.title}</h1>
<a on-click("open", "log.edit") class="color-menu">Edit</a>
<a on-click("open", "log.remove") class="color-menu">Remove</a>
<if-permitted user=input.user permission="log.edit">
<a on-click("open", "log.edit") class="color-menu">Edit</a>
</if-permitted>
<if-permitted user=input.user permission="log.remove">
<a on-click("open", "log.remove") class="color-menu">Remove</a>
</if-permitted>
</div> </div>
<annotation if(state.removed) level="error"> <annotation if(state.removed) level="error">
<p> <p>
@ -17,6 +21,7 @@
on-move("movePost", post) on-move("movePost", post)
on-remove("removePost", post) on-remove("removePost", post)
on-edited("postEdited") on-edited("postEdited")
user=input.user
post=post post=post
characters=state.log.characters characters=state.log.characters
last=(post.position === state.log.posts.length) last=(post.position === state.log.posts.length)

14
marko/page/logs-content/components/post/index.marko

@ -1,10 +1,16 @@
<div if(!state.removed) class=["post", component.kindClass("post-type")]> <div if(!state.removed) class=["post", component.kindClass("post-type")]>
<div class="post-meta-row"> <div class="post-meta-row">
<div class="options color-menu"> <div class="options color-menu">
<a on-click("move", -1, true)>Up</a>
<a on-click("move", +1, true)>Down</a>
<a on-click("open", "edit") >Edit</a>
<a on-click("open", "remove")>Remove</a>
<if-permitted user=input.user permission="post.move">
<a on-click("move", -1, true)>Up</a>
<a on-click("move", +1, true)>Down</a>
</if-permitted>
<if-permitted user=input.user permission="post.edit">
<a on-click("open", "edit") >Edit</a>
</if-permitted>
<if-permitted user=input.user permission="post.remove">
<a on-click("open", "remove")>Remove</a>
</if-permitted>
</div> </div>
<post-meta kind="timestamp" value=input.post.time /> <post-meta kind="timestamp" value=input.post.time />
<post-meta kind="nick" value=input.post.nick /> <post-meta kind="nick" value=input.post.nick />

13
marko/page/logs/components/logs-menu/index.marko

@ -1,11 +1,10 @@
<menu user=input.user> <menu user=input.user>
<menu-header>Logs</menu-header>
<menu-link selected=input.selected.index icon="L" href="/logs/">All</menu-link>
<menu-link dark on-click("select", "add") icon="+">Add Log</menu-link>
<menu-header key="logs">Logs</menu-header>
<menu-link key="logs" selected=input.selected.index icon="L" href="/logs/">All</menu-link>
<if-permitted key="if-permitted" user=input.user permission="log.add">
<menu-link key="add_log" dark on-click("select", "add") icon="+">Add Log</menu-link>
</if-permitted>
<menu-gap /> <menu-gap />
<menu-header>Filters</menu-header>
<menu-header key="filters">Filters</menu-header>
<menu-gap /> <menu-gap />
<menu-header>Data</menu-header>
<menu-link selected=input.selected.channels icon="C" href="/logs/characters/">Characters</menu-link>
<menu-link selected=input.selected.channels icon="#" href="/logs/channels">Channels</menu-link>
</menu> </menu>

8
marko/page/story-content/components/chapter/index.marko

@ -4,8 +4,12 @@ import moment from "moment"
<a class="anchor" id=input.chapter.id /> <a class="anchor" id=input.chapter.id />
<div class="metadata"> <div class="metadata">
<div class="options color-menu"> <div class="options color-menu">
<a on-click("open", "edit") >Edit</a>
<a on-click("open", "remove") >Remove</a>
<if-permitted user=input.user author=input.chapter.author permission="chapter.edit">
<a on-click("open", "edit") >Edit</a>
</if-permitted>
<if-permitted user=input.user author=input.chapter.author permission="chapter.remove">
<a on-click("open", "remove") >Remove</a>
</if-permitted>
</div> </div>
<chapter-meta kind="title" value=input.chapter.title /> <chapter-meta kind="title" value=input.chapter.title />
<chapter-meta kind="date" value=input.chapter.fictionalDate /> <chapter-meta kind="date" value=input.chapter.fictionalDate />

14
marko/page/story-content/components/page/index.marko

@ -3,17 +3,23 @@
<div class="story-content"> <div class="story-content">
<div class="header"> <div class="header">
<h1 class="color-primary">${state.story.name}</h1> <h1 class="color-primary">${state.story.name}</h1>
<a on-click("open", "story.edit") class="color-menu">Edit</a>
<a on-click("open", "story.remove") class="color-menu">Remove</a>
<if(!state.removed)>
<if-permitted user=input.user author=state.story.author permission="story.edit">
<a on-click("open", "story.edit") class="color-menu">Edit</a>
</if-permitted>
<if-permitted user=input.user author=state.story.author permission="story.remove">
<a on-click("open", "story.remove") class="color-menu">Remove</a>
</if-permitted>
</if>
</div> </div>
<story-tags tags=state.story.tags on-select("open", "story.tags")/>
<story-tags tags=state.story.tags author=state.story.author user=(!state.removed ? input.user : null) on-select("open", "story.tags")/>
<annotation if(state.removed) level="error"> <annotation if(state.removed) level="error">
<p> <p>
This story has been removed. Your browser has not refreshed the page yet, This story has been removed. Your browser has not refreshed the page yet,
so you can still read it (or back it up) before leaving the page. so you can still read it (or back it up) before leaving the page.
</p> </p>
</annotation> </annotation>
<chapter for(chapter in state.story.chapters) key=chapter.id chapter=chapter on-edit("updateChapter", chapter.id) on-remove("removeChapter", chapter.id)/>
<chapter for(chapter in state.story.chapters) key=chapter.id chapter=chapter user=(!state.removed ? input.user : null) on-edit("updateChapter", chapter.id) on-remove("removeChapter", chapter.id)/>
</div> </div>
<create-chapter-modal storyId=state.story.id enabled=(state.modal === "chapter.add") chapter=input.chapter on-close("close") on-add("addChapter") /> <create-chapter-modal storyId=state.story.id enabled=(state.modal === "chapter.add") chapter=input.chapter on-close("close") on-add("addChapter") />

11
marko/page/story-content/components/story-content-menu/index.marko

@ -3,5 +3,14 @@
<for(chapter in input.story.chapters | status-var=loop)> <for(chapter in input.story.chapters | status-var=loop)>
<menu-link key=chapter.id on-click("select", "chapter", chapter.id) href=("#" + chapter.id) selected=(state.selectedChapter === chapter.id) icon=(loop.getIndex()+1)>${component.chapterTitle(chapter)}</menu-link> <menu-link key=chapter.id on-click("select", "chapter", chapter.id) href=("#" + chapter.id) selected=(state.selectedChapter === chapter.id) icon=(loop.getIndex()+1)>${component.chapterTitle(chapter)}</menu-link>
</for> </for>
<menu-link dark key="_create" on-click("select", "add", null) icon="+">Add Chapter</menu-link>
<if(input.story.open)>
<if-permitted user=input.user permission=["member", "story.edit"]>
<menu-link dark key="_create" on-click("select", "add", null) icon="+">Add Chapter (Open)</menu-link>
</if-permitted>
</if>
<else>
<if-permitted user=input.user author=input.story.author permission="story.edit">
<menu-link dark key="_create" on-click("select", "add", null) icon="+">Add Chapter</menu-link>
</if-permitted>
</else>
</menu> </menu>

4
marko/page/story-content/components/story-tags/index.marko

@ -1,4 +1,6 @@
<div class="story-tags"> <div class="story-tags">
<a for(tag in input.tags) href=("/story/by-tag/" + tag.kind + "/" + encodeURIComponent(tag.name)) class=["tag", "color-tag-" + tag.kind.toLowerCase()]>${tag.name}</a> <a for(tag in input.tags) href=("/story/by-tag/" + tag.kind + "/" + encodeURIComponent(tag.name)) class=["tag", "color-tag-" + tag.kind.toLowerCase()]>${tag.name}</a>
<a on-click("select", "edit") class="option color-menu">Edit</a>
<if-permitted user=input.user author=input.author permission="story.edit">
<a on-click("select", "edit") class="option color-menu">Manage Tags</a>
</if-permitted>
</div> </div>

2
marko/page/story-content/components/story-tags/style.less

@ -3,6 +3,8 @@ div.story-tags {
margin-bottom: 1em; margin-bottom: 1em;
margin-top: -1em; margin-top: -1em;
min-height: 2em;
a.tag { a.tag {
display: inline-block; display: inline-block;
margin: 0.5em 0.5ch; margin: 0.5em 0.5ch;

4
marko/page/story/components/story-menu/index.marko

@ -11,7 +11,9 @@
</if> </if>
<menu-link for(tag in (input.menuTags||[])) key=(tag.kind+":"+tag.name) selected=(input.selected.tag === tag.kind+":"+tag.name) href=("/story/by-tag/"+tag.kind+"/"+tag.name) textClass=("color-tag-"+tag.kind.toLowerCase()) icon=tag.kind.charAt(0)>${tag.name}</menu-link> <menu-link for(tag in (input.menuTags||[])) key=(tag.kind+":"+tag.name) selected=(input.selected.tag === tag.kind+":"+tag.name) href=("/story/by-tag/"+tag.kind+"/"+tag.name) textClass=("color-tag-"+tag.kind.toLowerCase()) icon=tag.kind.charAt(0)>${tag.name}</menu-link>
<menu-gap /> <menu-gap />
<menu-link dark key="_create" on-click("open", "story.add") icon="+">Add Story</menu-link>
<if-permitted user=input.user permission=["member", "story.add"]>
<menu-link dark key="_create" on-click("open", "story.add") icon="+">Add Story</menu-link>
</if-permitted>
<menu-gap /> <menu-gap />
<menu-header>Categories</menu-header> <menu-header>Categories</menu-header>
<menu-link for(category in (input.categories||[])) key=(category.name) selected=(input.selected.category == category.name) href=("/story/by-category/"+category.name) icon=category.name.charAt(0)>${category.name}</menu-link> <menu-link for(category in (input.categories||[])) key=(category.name) selected=(input.selected.category == category.name) href=("/story/by-category/"+category.name) icon=category.name.charAt(0)>${category.name}</menu-link>

35
middleware/locals.js

@ -1,3 +1,5 @@
const config = require("../config")
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
if (res.marko) { if (res.marko) {
res.markoAsync = async(template, input) => { res.markoAsync = async(template, input) => {
@ -10,6 +12,10 @@ module.exports = (req, res, next) => {
locals[key] = await value locals[key] = await value
} }
} }
if (locals.user.permissions != null) {
locals.user.permissions = await locals.user.permissions
}
} catch(err) { } catch(err) {
if (JSON.stringify(err) === "{}") { if (JSON.stringify(err) === "{}") {
return next(err) return next(err)
@ -26,6 +32,7 @@ module.exports = (req, res, next) => {
res.locals.user = { res.locals.user = {
loggedIn: true, loggedIn: true,
name: req.user._json.name, name: req.user._json.name,
permissions: getPermissions(req.user._json.name),
} }
} else { } else {
res.locals.user = { res.locals.user = {
@ -35,3 +42,31 @@ module.exports = (req, res, next) => {
next() next()
} }
function getPermissions(user) {
return fetch(config.graphqlEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${generateToken(user, ["member"])}`,
},
body: '{"query":"query { token { user { permissions } } }", "variables": {}}',
credentials: "include",
}).then(res => {
return res.json()
}).then(json => {
return json.data.token.user.permissions
}).catch(err => {
console.error(err)
return []
})
}
const jwt = require("jsonwebtoken")
/**
* @param {string} user
* @param {string[]} permissions
*/
function generateToken(user, permissions) {
return jwt.sign({user, permissions, exp: Math.floor((Date.now() / 1000) + 1200)}, config.backend.secret, {header: {kid: config.backend.kid}})
}
Loading…
Cancel
Save