data, logs, logs-content, story, story-content: Added permission check in UI to hide disallowed actions.
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<if(state.permitted)>
|
||||
<include(input.renderBody) />
|
||||
</if>
|
||||
@@ -1,11 +1,13 @@
|
||||
<div class="menu">
|
||||
<include(input.renderBody) />
|
||||
<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>
|
||||
</if>
|
||||
<else-if(!input.allowNoUser)>
|
||||
<menu-blurb class="error">input.user is missing!</menu-blurb>
|
||||
</else-if>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
<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>
|
||||
<textarea key="description" placeholder="Description" on-change("change", "description") value=state.values.description />
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
<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>
|
||||
<edit-channel-modal enabled=(state.modal === "edit") channel=input.data on-edited("emit", "edited") on-close("close") />
|
||||
</if>
|
||||
@@ -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,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,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>
|
||||
<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>
|
||||
</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") />
|
||||
|
||||
@@ -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") />
|
||||
|
||||
@@ -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 />
|
||||
<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>
|
||||
@@ -1,8 +1,9 @@
|
||||
<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-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>
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
<div class="logs-content">
|
||||
<div class="header">
|
||||
<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>
|
||||
<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)
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
<div if(!state.removed) class=["post", component.kindClass("post-type")]>
|
||||
<div class="post-meta-row">
|
||||
<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>
|
||||
<post-meta kind="timestamp" value=input.post.time />
|
||||
<post-meta kind="nick" value=input.post.nick />
|
||||
|
||||
@@ -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,8 +4,12 @@ import moment from "moment"
|
||||
<a class="anchor" id=input.chapter.id />
|
||||
<div class="metadata">
|
||||
<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>
|
||||
<chapter-meta kind="title" value=input.chapter.title />
|
||||
<chapter-meta kind="date" value=input.chapter.fictionalDate />
|
||||
|
||||
@@ -3,17 +3,23 @@
|
||||
<div class="story-content">
|
||||
<div class="header">
|
||||
<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>
|
||||
<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") />
|
||||
|
||||
@@ -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>
|
||||
<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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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 />
|
||||
<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-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>
|
||||
|
||||
@@ -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 = {
|
||||
@@ -34,4 +41,32 @@ 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}})
|
||||
}
|
||||
Reference in New Issue
Block a user