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. 4
      marko/components/menu/index.marko
  4. 8
      marko/page/data/components/add-character-modal/index.marko
  5. 2
      marko/page/data/components/channel-list/index.marko
  6. 2
      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. 4
      marko/page/data/components/character/index.marko
  10. 2
      marko/page/data/components/characters-page/index.marko
  11. 6
      marko/page/data/components/data-menu/index.marko
  12. 3
      marko/page/logs-content/components/logs-content-menu/index.marko
  13. 5
      marko/page/logs-content/components/page/index.marko
  14. 6
      marko/page/logs-content/components/post/index.marko
  15. 13
      marko/page/logs/components/logs-menu/index.marko
  16. 4
      marko/page/story-content/components/chapter/index.marko
  17. 10
      marko/page/story-content/components/page/index.marko
  18. 9
      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. 2
      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>

4
marko/components/menu/index.marko

@ -1,8 +1,10 @@
<div class="menu">
<div class="menu-body">
<include(input.renderBody) />
</div>
<if(input.user != null)>
<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/logout" on-click("logoutClicked")>Logout <span class="weak">(${input.user.name})</span></menu-link>
</if>

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

@ -6,14 +6,16 @@
<label>Full Name</label>
<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>
<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 />
<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>
<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-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>

2
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 short weak=!input.data.logged short label="Logged" value=(input.data.logged ? "On" : "Off") />
<compact-list-options>
<if-permitted user=input.user permission="channel.edit">
<a on-click("open", "edit")>Edit</a>
</if-permitted>
</compact-list-options>
<edit-channel-modal enabled=(state.modal === "edit") channel=input.data on-edited("emit", "edited") on-close("close") />
</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") />
<main>
<channel-list channels=state.channels on-edited("channelEdited") />
<channel-list channels=state.channels user=input.user on-edited("channelEdited") />
</main>
<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") />

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

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

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

@ -4,9 +4,13 @@
<compact-list-property long label="Name" value=input.data.name />
<compact-list-property short label="Author" value=input.data.author />
<compact-list-options long>
<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>
</if-permitted>
</compact-list-options>
<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") />

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") />
<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>
<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") />

6
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="channels" selected=input.selected.channels icon="#" href="/data/channels/">Channels</menu-link>
<menu-gap />
<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>

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

@ -1,8 +1,9 @@
<menu user=input.user>
<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-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>

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

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

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

@ -1,10 +1,16 @@
<div if(!state.removed) class=["post", component.kindClass("post-type")]>
<div class="post-meta-row">
<div class="options color-menu">
<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>
<post-meta kind="timestamp" value=input.post.time />
<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-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-header>Filters</menu-header>
<menu-header key="filters">Filters</menu-header>
<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>

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

@ -4,8 +4,12 @@ import moment from "moment"
<a class="anchor" id=input.chapter.id />
<div class="metadata">
<div class="options color-menu">
<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>
<chapter-meta kind="title" value=input.chapter.title />
<chapter-meta kind="date" value=input.chapter.fictionalDate />

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

@ -3,17 +3,23 @@
<div class="story-content">
<div class="header">
<h1 class="color-primary">${state.story.name}</h1>
<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>
<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">
<p>
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.
</p>
</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>
<create-chapter-modal storyId=state.story.id enabled=(state.modal === "chapter.add") chapter=input.chapter on-close("close") on-add("addChapter") />

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

@ -3,5 +3,14 @@
<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>
</for>
<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>

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

@ -1,4 +1,6 @@
<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 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>

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

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

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

@ -11,7 +11,9 @@
</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-gap />
<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-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>

35
middleware/locals.js

@ -1,3 +1,5 @@
const config = require("../config")
module.exports = (req, res, next) => {
if (res.marko) {
res.markoAsync = async(template, input) => {
@ -10,6 +12,10 @@ module.exports = (req, res, next) => {
locals[key] = await value
}
}
if (locals.user.permissions != null) {
locals.user.permissions = await locals.user.permissions
}
} catch(err) {
if (JSON.stringify(err) === "{}") {
return next(err)
@ -26,6 +32,7 @@ module.exports = (req, res, next) => {
res.locals.user = {
loggedIn: true,
name: req.user._json.name,
permissions: getPermissions(req.user._json.name),
}
} else {
res.locals.user = {
@ -35,3 +42,31 @@ module.exports = (req, res, 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