#132 fine-grained permissions
This commit is contained in:
parent
2a31d688ba
commit
257db4099e
|
@ -26,7 +26,7 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="$admin()">
|
<p v-if="$isGranted('panel') || $isGranted('users')">
|
||||||
<nuxt-link to="/admin" class="badge badge-primary"><T>user.account.admin</T></nuxt-link>
|
<nuxt-link to="/admin" class="badge badge-primary"><T>user.account.admin</T></nuxt-link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<Loading :value="nounsRaw">
|
<Loading :value="nounsRaw">
|
||||||
<section v-if="$admin()" class="px-3">
|
<section v-if="$isGranted('nouns')" class="px-3">
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<strong>{{ nounsCountApproved() }}</strong> <T>nouns.approved</T>,
|
<strong>{{ nounsCountApproved() }}</strong> <T>nouns.approved</T>,
|
||||||
<strong>{{ nounsCountPending() }}</strong> <T>nouns.pending</T>.
|
<strong>{{ nounsCountPending() }}</strong> <T>nouns.pending</T>.
|
||||||
|
@ -142,7 +142,7 @@
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</li>
|
</li>
|
||||||
-->
|
-->
|
||||||
<template v-if="$admin()">
|
<template v-if="$isGranted('nouns')">
|
||||||
<li v-if="!s.el.approved">
|
<li v-if="!s.el.approved">
|
||||||
<button class="btn btn-concise btn-success btn-sm m-1" @click="approve(s.el)">
|
<button class="btn btn-concise btn-success btn-sm m-1" @click="approve(s.el)">
|
||||||
<Icon v="check"/>
|
<Icon v="check"/>
|
||||||
|
@ -166,7 +166,7 @@
|
||||||
<button class="btn btn-concise btn-outline-primary btn-sm m-1" @click="edit(s.el)">
|
<button class="btn btn-concise btn-outline-primary btn-sm m-1" @click="edit(s.el)">
|
||||||
<Icon v="pen"/>
|
<Icon v="pen"/>
|
||||||
<span class="btn-label">
|
<span class="btn-label">
|
||||||
<T v-if="$admin()">crud.edit</T>
|
<T v-if="$isGranted('nouns')">crud.edit</T>
|
||||||
<T v-else>nouns.edit</T>
|
<T v-else>nouns.edit</T>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<Loading :value="entriesRaw">
|
<Loading :value="entriesRaw">
|
||||||
<section v-if="$admin()" class="px-3">
|
<section v-if="$isGranted('inclusive')" class="px-3">
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<strong>{{ entriesCountApproved() }}</strong> <T>nouns.approved</T>,
|
<strong>{{ entriesCountApproved() }}</strong> <T>nouns.approved</T>,
|
||||||
<strong>{{ entriesCountPending() }}</strong> <T>nouns.pending</T>.
|
<strong>{{ entriesCountPending() }}</strong> <T>nouns.pending</T>.
|
||||||
|
@ -126,7 +126,7 @@
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</li>
|
</li>
|
||||||
-->
|
-->
|
||||||
<template v-if="$admin()">
|
<template v-if="$isGranted('inclusive')">
|
||||||
<li v-if="!s.el.approved">
|
<li v-if="!s.el.approved">
|
||||||
<button class="btn btn-concise btn-success btn-sm m-1" @click="approve(s.el)">
|
<button class="btn btn-concise btn-success btn-sm m-1" @click="approve(s.el)">
|
||||||
<Icon v="check"/>
|
<Icon v="check"/>
|
||||||
|
@ -150,7 +150,7 @@
|
||||||
<button class="btn btn-concise btn-outline-primary btn-sm m-1" @click="edit(s.el)">
|
<button class="btn btn-concise btn-outline-primary btn-sm m-1" @click="edit(s.el)">
|
||||||
<Icon v="pen"/>
|
<Icon v="pen"/>
|
||||||
<span class="btn-label">
|
<span class="btn-label">
|
||||||
<T v-if="$admin()">crud.edit</T>
|
<T v-if="$isGranted('inclusive')">crud.edit</T>
|
||||||
<T v-else>nouns.edit</T>
|
<T v-else>nouns.edit</T>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
<template>
|
||||||
|
<form v-if="$isGranted('*')">
|
||||||
|
<ListInput v-model="roles" v-slot="s" :prototype="{locale: config.locale, area: '*'}">
|
||||||
|
<select v-model="s.val.locale" class="form-control">
|
||||||
|
<option v-for="l in allLocales" :value="l">{{l}}</option>
|
||||||
|
</select>
|
||||||
|
<select v-model="s.val.area" class="form-control">
|
||||||
|
<option v-for="a in allAreas" :value="a">{{a}}</option>
|
||||||
|
</select>
|
||||||
|
</ListInput>
|
||||||
|
<button class="btn btn-outline-primary" @click.prevent="save">Save</button>
|
||||||
|
</form>
|
||||||
|
<ul v-else class="list-unstyled">
|
||||||
|
<li v-for="role in user.roles.split('|')">
|
||||||
|
<span class="badge badge-primary">{{role}}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
user: { required: true },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
roles: (this.user.roles ? this.user.roles.split('|') : []).map(r => {
|
||||||
|
if (r === '*') { r = '*-*'; }
|
||||||
|
const [ locale, area ] = r.split('-');
|
||||||
|
return { locale, area };
|
||||||
|
}),
|
||||||
|
allLocales: ['*', ...Object.keys(this.locales)],
|
||||||
|
allAreas: [
|
||||||
|
'*',
|
||||||
|
'panel',
|
||||||
|
'users',
|
||||||
|
'sources',
|
||||||
|
'nouns',
|
||||||
|
'terms',
|
||||||
|
'inclusive',
|
||||||
|
'census',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async save() {
|
||||||
|
const roles = this.roles.map(r => {
|
||||||
|
if (r.locale === '*' && r.area === '*') {
|
||||||
|
return '*';
|
||||||
|
}
|
||||||
|
return `${r.locale}-${r.area}`;
|
||||||
|
}).join('|');
|
||||||
|
|
||||||
|
await this.$confirm(this.$t('admin.user.confirmRole', {username: this.user.username, role: roles}));
|
||||||
|
|
||||||
|
const response = await this.$axios.$post(`/user/${this.user.id}/set-roles`, { roles: roles});
|
||||||
|
|
||||||
|
this.user.roles = roles;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="my-2" v-if="!deleted">
|
<div class="my-2" v-if="!deleted">
|
||||||
<Icon :v="source.icon()"/>
|
<Icon :v="source.icon()"/>
|
||||||
<strong><template v-if="source.author">{{source.author.replace('^', '')}}</template><span v-if="source.author"> – </span><em><a v-if="source.link" :href="source.link" target="_blank" rel="noopener">{{source.title}}</a><span v-else>{{source.title}}</span></em></strong><template v-if="source.extra"> ({{source.extra}})</template>, {{source.year}}<template v-if="source.comment">; {{source.comment}}</template>
|
<strong><template v-if="source.author">{{source.author.replace('^', '')}}</template><span v-if="source.author"> – </span><em><a v-if="source.link" :href="source.link" target="_blank" rel="noopener">{{source.title}}</a><span v-else>{{source.title}}</span></em></strong><template v-if="source.extra"> ({{source.extra}})</template>, {{source.year}}<template v-if="source.comment">; {{source.comment}}</template>
|
||||||
<ul class="list-inline" v-if="manage && $admin()">
|
<ul class="list-inline" v-if="manage && $isGranted('sources')">
|
||||||
<li v-if="!source.approved" class="list-inline-item">
|
<li v-if="!source.approved" class="list-inline-item">
|
||||||
<span class="badge badge-danger">
|
<span class="badge badge-danger">
|
||||||
<Icon v="map-marker-question"/>
|
<Icon v="map-marker-question"/>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<Loading :value="entriesRaw">
|
<Loading :value="entriesRaw">
|
||||||
<section v-if="$admin()" class="px-3">
|
<section v-if="$isGranted('terms')" class="px-3">
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<strong>{{ entriesCountApproved() }}</strong> <T>nouns.approved</T>,
|
<strong>{{ entriesCountApproved() }}</strong> <T>nouns.approved</T>,
|
||||||
<strong>{{ entriesCountPending() }}</strong> <T>nouns.pending</T>.
|
<strong>{{ entriesCountPending() }}</strong> <T>nouns.pending</T>.
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</li>
|
</li>
|
||||||
-->
|
-->
|
||||||
<template v-if="$admin()">
|
<template v-if="$$isGranted('terms')">
|
||||||
<li v-if="!s.el.approved">
|
<li v-if="!s.el.approved">
|
||||||
<button class="btn btn-concise btn-success btn-sm m-1" @click="approve(s.el)">
|
<button class="btn btn-concise btn-success btn-sm m-1" @click="approve(s.el)">
|
||||||
<Icon v="check"/>
|
<Icon v="check"/>
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
<button class="btn btn-concise btn-outline-primary btn-sm m-1" @click="edit(s.el)">
|
<button class="btn btn-concise btn-outline-primary btn-sm m-1" @click="edit(s.el)">
|
||||||
<Icon v="pen"/>
|
<Icon v="pen"/>
|
||||||
<span class="btn-label">
|
<span class="btn-label">
|
||||||
<T v-if="$admin()">crud.edit</T>
|
<T v-if="$isGranted('terms')">crud.edit</T>
|
||||||
<T v-else>nouns.edit</T>
|
<T v-else>nouns.edit</T>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
<textarea v-model="form.definition" class="form-control form-control-sm" required rows="3"></textarea>
|
<textarea v-model="form.definition" class="form-control form-control-sm" required rows="3"></textarea>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="$admin()">
|
<tr v-if="$isGranted('terms')">
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
<T>profile.flags</T>
|
<T>profile.flags</T>
|
||||||
<ListInput v-model="form.flags" v-slot="s"/>
|
<ListInput v-model="form.flags" v-slot="s"/>
|
||||||
|
|
|
@ -429,7 +429,7 @@ admin:
|
||||||
user:
|
user:
|
||||||
user: 'User'
|
user: 'User'
|
||||||
email: 'Email'
|
email: 'Email'
|
||||||
roles: 'Role'
|
roles: 'Permissions'
|
||||||
profiles: 'Profiles'
|
profiles: 'Profiles'
|
||||||
confirmRole: 'Are you sure you want to switch @%username%''s role to "%role%"?'
|
confirmRole: 'Are you sure you want to switch @%username%''s role to "%role%"?'
|
||||||
|
|
||||||
|
|
|
@ -990,7 +990,7 @@ admin:
|
||||||
user:
|
user:
|
||||||
user: 'Użytkownicze'
|
user: 'Użytkownicze'
|
||||||
email: 'Email'
|
email: 'Email'
|
||||||
roles: 'Rola'
|
roles: 'Uprawnienia'
|
||||||
profiles: 'Profile'
|
profiles: 'Profile'
|
||||||
confirmRole: 'Czy na pewno chcesz zmienić rolę osoby @%username% na "%role%"?'
|
confirmRole: 'Czy na pewno chcesz zmienić rolę osoby @%username% na "%role%"?'
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
-- Up
|
||||||
|
|
||||||
|
UPDATE users SET roles = '*' WHERE roles = 'admin';
|
||||||
|
UPDATE users SET roles = '' WHERE roles = 'user';
|
||||||
|
|
||||||
|
-- Down
|
||||||
|
|
||||||
|
UPDATE users SET roles = 'admin' WHERE roles = '*';
|
||||||
|
UPDATE users SET roles = 'user' WHERE roles = '';
|
|
@ -1,5 +1,6 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import t from "../src/translator";
|
import config from '../data/config.suml';
|
||||||
|
import {isGranted} from "../src/helpers";
|
||||||
|
|
||||||
export default ({app, store}) => {
|
export default ({app, store}) => {
|
||||||
const token = app.$cookies.get('token');
|
const token = app.$cookies.get('token');
|
||||||
|
@ -11,7 +12,7 @@ export default ({app, store}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
Vue.prototype.$user = _ => store.state.user;
|
Vue.prototype.$user = _ => store.state.user;
|
||||||
Vue.prototype.$admin = _ => {
|
Vue.prototype.$isGranted = (area) => {
|
||||||
return store.state.user && store.state.user.authenticated && store.state.user.roles === 'admin';
|
return store.state.user && store.state.user.authenticated && isGranted(store.state.user, config.locale, area);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,34 @@
|
||||||
<template>
|
<template>
|
||||||
<NotFound v-if="!$admin()"/>
|
<NotFound v-if="!$isGranted('panel') && !$isGranted('users')"/>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<h2>
|
<h2>
|
||||||
<Icon v="user-cog"/>
|
<Icon v="user-cog"/>
|
||||||
<T>admin.header</T>
|
<T>admin.header</T>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<section>
|
<section v-if="$isGranted('users')">
|
||||||
<details class="border mb-3">
|
<details class="border mb-3">
|
||||||
<summary class="bg-light p-3">
|
<summary class="bg-light p-3">
|
||||||
<Icon v="users"/>
|
<Icon v="users"/>
|
||||||
Users
|
Users
|
||||||
({{stats.users.overall}}, {{stats.users.admins}} admins)
|
({{stats.users.overall}} overall, {{stats.users.admins}} admins, {{visibleUsers.length}} visible)
|
||||||
</summary>
|
</summary>
|
||||||
<div class="border-top">
|
<div class="border-top">
|
||||||
<input class="form-control mt-4" v-model="userFilter" :placeholder="$t('crud.filterLong')"/>
|
<div class="input-group mt-4">
|
||||||
|
<input class="form-control" v-model="userFilter" :placeholder="$t('crud.filterLong')"/>
|
||||||
|
<span class="input-group-append">
|
||||||
|
<button :class="['btn', adminsFilter ? 'btn-secondary' : 'btn-outline-secondary']"
|
||||||
|
@click="adminsFilter = !adminsFilter"
|
||||||
|
>
|
||||||
|
Only admins
|
||||||
|
</button>
|
||||||
|
<button :class="['btn', localeFilter ? 'btn-secondary' : 'btn-outline-secondary']"
|
||||||
|
@click="localeFilter = !localeFilter"
|
||||||
|
>
|
||||||
|
Only this version
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<Table :data="visibleUsers" :columns="4">
|
<Table :data="visibleUsers" :columns="4">
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<th class="text-nowrap">
|
<th class="text-nowrap">
|
||||||
|
@ -49,10 +63,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" :class="['badge', s.el.roles === 'admin' ? 'badge-primary' : 'badge-light']"
|
<Roles :user="s.el"/>
|
||||||
@click.prevent="setRole(s.el.id, s.el.roles === 'admin' ? 'user' : 'admin')">
|
|
||||||
{{s.el.roles}}
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled">
|
||||||
|
@ -117,7 +128,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {head} from "../src/helpers";
|
import {head, isGranted} from "../src/helpers";
|
||||||
import {socialProviders} from "../src/data";
|
import {socialProviders} from "../src/data";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -125,34 +136,34 @@
|
||||||
return {
|
return {
|
||||||
socialProviders,
|
socialProviders,
|
||||||
userFilter: '',
|
userFilter: '',
|
||||||
|
localeFilter: true,
|
||||||
|
adminsFilter: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async asyncData({ app, store }) {
|
async asyncData({ app, store }) {
|
||||||
if (!store.state.user || store.state.user.roles !== 'admin') {
|
let stats = { users: {}};
|
||||||
return {};
|
let users = {};
|
||||||
}
|
|
||||||
|
|
||||||
const stats = await app.$axios.$get(`/admin/stats`);
|
try {
|
||||||
|
stats = await app.$axios.$get(`/admin/stats`);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
const users = await app.$axios.$get(`/admin/users`);
|
try {
|
||||||
|
users = await app.$axios.$get(`/admin/users`);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
stats,
|
stats,
|
||||||
users,
|
users,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
async setRole(userId, role) {
|
|
||||||
await this.$confirm(this.$t('admin.user.confirmRole', {username: this.users[userId].username, role}));
|
|
||||||
|
|
||||||
const response = await this.$axios.$post(`/user/${userId}/set-roles`, { roles: role });
|
|
||||||
|
|
||||||
this.users[userId].roles = role;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
visibleUsers() {
|
visibleUsers() {
|
||||||
return Object.values(this.users).filter(u => u.username.toLowerCase().includes(this.userFilter.toLowerCase()));
|
return Object.values(this.users).filter(u =>
|
||||||
|
u.username.toLowerCase().includes(this.userFilter.toLowerCase())
|
||||||
|
&& (!this.adminsFilter || u.roles !== '')
|
||||||
|
&& (!this.localeFilter || u.profiles.includes(this.config.locale))
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
head() {
|
head() {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<template v-if="q === null">
|
<template v-if="q === null">
|
||||||
<section v-if="$admin()">
|
<section v-if="$isGranted('census')">
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
{{countResponses}}
|
{{countResponses}}
|
||||||
<T>census.replies</T>
|
<T>census.replies</T>
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section v-if="$admin()" class="px-3">
|
<section v-if="$isGranted('sources')" class="px-3">
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<strong>{{ sourceLibrary.countApproved }}</strong> <T>nouns.approved</T>,
|
<strong>{{ sourceLibrary.countApproved }}</strong> <T>nouns.approved</T>,
|
||||||
<a href="#" @click.prevent="filter='__awaiting__'">
|
<a href="#" @click.prevent="filter='__awaiting__'">
|
||||||
|
|
|
@ -6,7 +6,7 @@ import cookieParser from 'cookie-parser';
|
||||||
import grant from "grant";
|
import grant from "grant";
|
||||||
import router from "./routes/user";
|
import router from "./routes/user";
|
||||||
import { loadSuml } from './loader';
|
import { loadSuml } from './loader';
|
||||||
import {buildLocaleList} from "../src/helpers";
|
import {buildLocaleList, isGranted} from "../src/helpers";
|
||||||
|
|
||||||
global.config = loadSuml('config');
|
global.config = loadSuml('config');
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ app.use(async function (req, res, next) {
|
||||||
req.locales = buildLocaleList(global.config.locale);
|
req.locales = buildLocaleList(global.config.locale);
|
||||||
req.rawUser = authenticate(req);
|
req.rawUser = authenticate(req);
|
||||||
req.user = req.rawUser && req.rawUser.authenticated ? req.rawUser : null;
|
req.user = req.rawUser && req.rawUser.authenticated ? req.rawUser : null;
|
||||||
req.admin = req.user && req.user.roles === 'admin';
|
req.isGranted = (area, locale = global.config.locale) => req.user && isGranted(req.user, locale, area);
|
||||||
req.db = await dbConnection();
|
req.db = await dbConnection();
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {now, sortByValue} from "../../src/helpers";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get('/admin/users', async (req, res) => {
|
router.get('/admin/users', async (req, res) => {
|
||||||
if (!req.admin) {
|
if (!req.isGranted('users')) {
|
||||||
return res.status(401).json({error: 'Unauthorised'});
|
return res.status(401).json({error: 'Unauthorised'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ router.get('/admin/users', async (req, res) => {
|
||||||
SELECT u.id, u.username, u.email, u.roles, u.avatarSource, p.locale
|
SELECT u.id, u.username, u.email, u.roles, u.avatarSource, p.locale
|
||||||
FROM users u
|
FROM users u
|
||||||
LEFT JOIN profiles p ON p.userId = u.id
|
LEFT JOIN profiles p ON p.userId = u.id
|
||||||
ORDER BY u.roles ASC, u.id DESC
|
ORDER BY u.id DESC
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const authenticators = await req.db.all(SQL`
|
const authenticators = await req.db.all(SQL`
|
||||||
|
@ -47,18 +47,19 @@ router.get('/admin/users', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/admin/stats', async (req, res) => {
|
router.get('/admin/stats', async (req, res) => {
|
||||||
if (!req.admin) {
|
if (!req.isGranted('panel')) {
|
||||||
return res.status(401).json({error: 'Unauthorised'});
|
return res.status(401).json({error: 'Unauthorised'});
|
||||||
}
|
}
|
||||||
|
|
||||||
const users = {
|
const users = {
|
||||||
overall: (await req.db.get(SQL`SELECT count(*) AS c FROM users`)).c,
|
overall: (await req.db.get(SQL`SELECT count(*) AS c FROM users`)).c,
|
||||||
admins: (await req.db.get(SQL`SELECT count(*) AS c FROM users WHERE roles=${'admin'}`)).c,
|
admins: (await req.db.get(SQL`SELECT count(*) AS c FROM users WHERE roles!=''`)).c,
|
||||||
};
|
};
|
||||||
|
|
||||||
const locales = {};
|
const locales = {};
|
||||||
for (let locale in req.locales) {
|
for (let locale in req.locales) {
|
||||||
if (!req.locales.hasOwnProperty(locale)) { continue; }
|
if (!req.locales.hasOwnProperty(locale)) { continue; }
|
||||||
|
if (!req.isGranted('panel', locale)) { continue; }
|
||||||
const profiles = await req.db.all(SQL`SELECT pronouns, flags FROM profiles WHERE locale=${locale}`);
|
const profiles = await req.db.all(SQL`SELECT pronouns, flags FROM profiles WHERE locale=${locale}`);
|
||||||
const pronouns = {}
|
const pronouns = {}
|
||||||
const flags = {}
|
const flags = {}
|
||||||
|
|
|
@ -26,7 +26,7 @@ router.get('/inclusive', async (req, res) => {
|
||||||
SELECT i.*, u.username AS author FROM inclusive i
|
SELECT i.*, u.username AS author FROM inclusive i
|
||||||
LEFT JOIN users u ON i.author_id = u.id
|
LEFT JOIN users u ON i.author_id = u.id
|
||||||
WHERE i.locale = ${req.config.locale}
|
WHERE i.locale = ${req.config.locale}
|
||||||
AND i.approved >= ${req.admin ? 0 : 1}
|
AND i.approved >= ${req.isGranted('inclusive') ? 0 : 1}
|
||||||
AND i.deleted = 0
|
AND i.deleted = 0
|
||||||
ORDER BY i.approved, i.insteadOf
|
ORDER BY i.approved, i.insteadOf
|
||||||
`));
|
`));
|
||||||
|
@ -38,7 +38,7 @@ router.get('/inclusive/search/:term', async (req, res) => {
|
||||||
SELECT i.*, u.username AS author FROM inclusive i
|
SELECT i.*, u.username AS author FROM inclusive i
|
||||||
LEFT JOIN users u ON i.author_id = u.id
|
LEFT JOIN users u ON i.author_id = u.id
|
||||||
WHERE i.locale = ${req.config.locale}
|
WHERE i.locale = ${req.config.locale}
|
||||||
AND i.approved >= ${req.admin ? 0 : 1}
|
AND i.approved >= ${req.isGranted('inclusive') ? 0 : 1}
|
||||||
AND i.deleted = 0
|
AND i.deleted = 0
|
||||||
AND (i.insteadOf like ${term} OR i.say like ${term})
|
AND (i.insteadOf like ${term} OR i.say like ${term})
|
||||||
ORDER BY i.approved, i.insteadOf
|
ORDER BY i.approved, i.insteadOf
|
||||||
|
@ -61,7 +61,7 @@ router.post('/inclusive/submit', async (req, res) => {
|
||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
if (req.admin) {
|
if (req.isGranted('inclusive')) {
|
||||||
await approve(req.db, id);
|
await approve(req.db, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ router.post('/inclusive/submit', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/inclusive/hide/:id', async (req, res) => {
|
router.post('/inclusive/hide/:id', async (req, res) => {
|
||||||
if (!req.admin) {
|
if (!req.isGranted('inclusive')) {
|
||||||
res.status(401).json({error: 'Unauthorised'});
|
res.status(401).json({error: 'Unauthorised'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ router.post('/inclusive/hide/:id', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/inclusive/approve/:id', async (req, res) => {
|
router.post('/inclusive/approve/:id', async (req, res) => {
|
||||||
if (!req.admin) {
|
if (!req.isGranted('inclusive')) {
|
||||||
res.status(401).json({error: 'Unauthorised'});
|
res.status(401).json({error: 'Unauthorised'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ router.post('/inclusive/approve/:id', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/inclusive/remove/:id', async (req, res) => {
|
router.post('/inclusive/remove/:id', async (req, res) => {
|
||||||
if (!req.admin) {
|
if (!req.isGranted('inclusive')) {
|
||||||
res.status(401).json({error: 'Unauthorised'});
|
res.status(401).json({error: 'Unauthorised'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ router.get('/nouns', async (req, res) => {
|
||||||
LEFT JOIN users u ON n.author_id = u.id
|
LEFT JOIN users u ON n.author_id = u.id
|
||||||
WHERE n.locale = ${req.config.locale}
|
WHERE n.locale = ${req.config.locale}
|
||||||
AND n.deleted = 0
|
AND n.deleted = 0
|
||||||
AND n.approved >= ${req.admin ? 0 : 1}
|
AND n.approved >= ${req.isGranted('nouns') ? 0 : 1}
|
||||||
ORDER BY n.approved, n.masc
|
ORDER BY n.approved, n.masc
|
||||||
`));
|
`));
|
||||||
});
|
});
|
||||||
|
@ -42,7 +42,7 @@ router.get('/nouns/search/:term', async (req, res) => {
|
||||||
SELECT n.*, u.username AS author FROM nouns n
|
SELECT n.*, u.username AS author FROM nouns n
|
||||||
LEFT JOIN users u ON n.author_id = u.id
|
LEFT JOIN users u ON n.author_id = u.id
|
||||||
WHERE n.locale = ${req.config.locale}
|
WHERE n.locale = ${req.config.locale}
|
||||||
AND n.approved >= ${req.admin ? 0 : 1}
|
AND n.approved >= ${req.isGranted('nouns') ? 0 : 1}
|
||||||
AND n.deleted = 0
|
AND n.deleted = 0
|
||||||
AND (n.masc like ${term} OR n.fem like ${term} OR n.neutr like ${term} OR n.mascPl like ${term} OR n.femPl like ${term} OR n.neutrPl like ${term})
|
AND (n.masc like ${term} OR n.fem like ${term} OR n.neutr like ${term} OR n.mascPl like ${term} OR n.femPl like ${term} OR n.neutrPl like ${term})
|
||||||
ORDER BY n.approved, n.masc
|
ORDER BY n.approved, n.masc
|
||||||
|
@ -65,7 +65,7 @@ router.post('/nouns/submit', async (req, res) => {
|
||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
if (req.admin) {
|
if (req.isGranted('nouns')) {
|
||||||
await approve(req.db, id);
|
await approve(req.db, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ router.post('/nouns/submit', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/nouns/hide/:id', async (req, res) => {
|
router.post('/nouns/hide/:id', async (req, res) => {
|
||||||
if (!req.admin) {
|
if (!req.isGranted('nouns')) {
|
||||||
res.status(401).json({error: 'Unauthorised'});
|
res.status(401).json({error: 'Unauthorised'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ router.post('/nouns/hide/:id', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/nouns/approve/:id', async (req, res) => {
|
router.post('/nouns/approve/:id', async (req, res) => {
|
||||||
if (!req.admin) {
|
if (!req.isGranted('nouns')) {
|
||||||
res.status(401).json({error: 'Unauthorised'});
|
res.status(401).json({error: 'Unauthorised'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ router.post('/nouns/approve/:id', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/nouns/remove/:id', async (req, res) => {
|
router.post('/nouns/remove/:id', async (req, res) => {
|
||||||
if (!req.admin) {
|
if (!req.isGranted('nouns')) {
|
||||||
res.status(401).json({error: 'Unauthorised'});
|
res.status(401).json({error: 'Unauthorised'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ router.get('/nouns/:word.png', async (req, res) => {
|
||||||
const noun = (await req.db.all(SQL`
|
const noun = (await req.db.all(SQL`
|
||||||
SELECT * FROM nouns
|
SELECT * FROM nouns
|
||||||
WHERE locale = ${req.config.locale}
|
WHERE locale = ${req.config.locale}
|
||||||
AND approved >= ${req.admin ? 0 : 1}
|
AND approved >= ${req.isGranted('nouns') ? 0 : 1}
|
||||||
AND (masc like ${term} OR fem like ${term} OR neutr like ${term} OR mascPl like ${term} OR femPl like ${term} OR neutrPl like ${term})
|
AND (masc like ${term} OR fem like ${term} OR neutr like ${term} OR mascPl like ${term} OR femPl like ${term} OR neutrPl like ${term})
|
||||||
ORDER BY masc
|
ORDER BY masc
|
||||||
`)).filter(noun =>
|
`)).filter(noun =>
|
||||||
|
|
|
@ -26,7 +26,7 @@ router.get('/sources', async (req, res) => {
|
||||||
LEFT JOIN users u ON s.submitter_id = u.id
|
LEFT JOIN users u ON s.submitter_id = u.id
|
||||||
WHERE s.locale = ${req.config.locale}
|
WHERE s.locale = ${req.config.locale}
|
||||||
AND s.deleted = 0
|
AND s.deleted = 0
|
||||||
AND s.approved >= ${req.admin ? 0 : 1}
|
AND s.approved >= ${req.isGranted('sources') ? 0 : 1}
|
||||||
`));
|
`));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ router.get('/sources/:id', async (req, res) => {
|
||||||
LEFT JOIN users u ON s.submitter_id = u.id
|
LEFT JOIN users u ON s.submitter_id = u.id
|
||||||
WHERE s.locale = ${req.config.locale}
|
WHERE s.locale = ${req.config.locale}
|
||||||
AND s.deleted = 0
|
AND s.deleted = 0
|
||||||
AND s.approved >= ${req.admin ? 0 : 1}
|
AND s.approved >= ${req.isGranted('sources') ? 0 : 1}
|
||||||
AND s.id = ${req.params.id}
|
AND s.id = ${req.params.id}
|
||||||
`));
|
`));
|
||||||
});
|
});
|
||||||
|
@ -53,7 +53,7 @@ router.post('/sources/submit', async (req, res) => {
|
||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
if (req.admin) {
|
if (req.isGranted('sources')) {
|
||||||
await approve(req.db, id);
|
await approve(req.db, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ router.post('/sources/submit', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/sources/hide/:id', async (req, res) => {
|
router.post('/sources/hide/:id', async (req, res) => {
|
||||||
if (!req.admin) {
|
if (!req.isGranted('sources')) {
|
||||||
res.status(401).json({error: 'Unauthorised'});
|
res.status(401).json({error: 'Unauthorised'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ router.post('/sources/hide/:id', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/sources/approve/:id', async (req, res) => {
|
router.post('/sources/approve/:id', async (req, res) => {
|
||||||
if (!req.admin) {
|
if (!req.isGranted('sources')) {
|
||||||
res.status(401).json({error: 'Unauthorised'});
|
res.status(401).json({error: 'Unauthorised'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ router.post('/sources/approve/:id', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/sources/remove/:id', async (req, res) => {
|
router.post('/sources/remove/:id', async (req, res) => {
|
||||||
if (!req.admin) {
|
if (!req.isGranted('sources')) {
|
||||||
res.status(401).json({error: 'Unauthorised'});
|
res.status(401).json({error: 'Unauthorised'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ router.get('/terms', async (req, res) => {
|
||||||
SELECT i.*, u.username AS author FROM terms i
|
SELECT i.*, u.username AS author FROM terms i
|
||||||
LEFT JOIN users u ON i.author_id = u.id
|
LEFT JOIN users u ON i.author_id = u.id
|
||||||
WHERE i.locale = ${req.config.locale}
|
WHERE i.locale = ${req.config.locale}
|
||||||
AND i.approved >= ${req.admin ? 0 : 1}
|
AND i.approved >= ${req.isGranted('terms') ? 0 : 1}
|
||||||
AND i.deleted = 0
|
AND i.deleted = 0
|
||||||
ORDER BY i.term
|
ORDER BY i.term
|
||||||
`));
|
`));
|
||||||
|
@ -38,7 +38,7 @@ router.get('/terms/search/:term', async (req, res) => {
|
||||||
SELECT i.*, u.username AS author FROM terms i
|
SELECT i.*, u.username AS author FROM terms i
|
||||||
LEFT JOIN users u ON i.author_id = u.id
|
LEFT JOIN users u ON i.author_id = u.id
|
||||||
WHERE i.locale = ${req.config.locale}
|
WHERE i.locale = ${req.config.locale}
|
||||||
AND i.approved >= ${req.admin ? 0 : 1}
|
AND i.approved >= ${req.isGranted('terms') ? 0 : 1}
|
||||||
AND i.deleted = 0
|
AND i.deleted = 0
|
||||||
AND (i.term like ${term} OR i.original like ${term})
|
AND (i.term like ${term} OR i.original like ${term})
|
||||||
ORDER BY i.term
|
ORDER BY i.term
|
||||||
|
@ -61,7 +61,7 @@ router.post('/terms/submit', async (req, res) => {
|
||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
if (req.admin) {
|
if (req.isGranted('terms')) {
|
||||||
await approve(req.db, id);
|
await approve(req.db, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ router.post('/terms/submit', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/terms/hide/:id', async (req, res) => {
|
router.post('/terms/hide/:id', async (req, res) => {
|
||||||
if (!req.admin) {
|
if (!req.isGranted('terms')) {
|
||||||
res.status(401).json({error: 'Unauthorised'});
|
res.status(401).json({error: 'Unauthorised'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ router.post('/terms/hide/:id', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/terms/approve/:id', async (req, res) => {
|
router.post('/terms/approve/:id', async (req, res) => {
|
||||||
if (!req.admin) {
|
if (!req.isGranted('terms')) {
|
||||||
res.status(401).json({error: 'Unauthorised'});
|
res.status(401).json({error: 'Unauthorised'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ router.post('/terms/approve/:id', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/terms/remove/:id', async (req, res) => {
|
router.post('/terms/remove/:id', async (req, res) => {
|
||||||
if (!req.admin) {
|
if (!req.isGranted('terms')) {
|
||||||
res.status(401).json({error: 'Unauthorised'});
|
res.status(401).json({error: 'Unauthorised'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -276,7 +276,7 @@ router.post('/user/delete', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/user/:id/set-roles', async (req, res) => {
|
router.post('/user/:id/set-roles', async (req, res) => {
|
||||||
if (!req.admin) {
|
if (!req.isGranted('*')) {
|
||||||
return res.status(401).json({error: 'Unauthorised'});
|
return res.status(401).json({error: 'Unauthorised'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -175,3 +175,21 @@ export const shuffle = a => {
|
||||||
}
|
}
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isGranted = (user, locale, area) => {
|
||||||
|
if (area === '*') {
|
||||||
|
return user.roles.split('|').includes('*');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let permission of user.roles.split('|')) {
|
||||||
|
if (permission === '*') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const [ permissionLocale, permissionArea ] = permission.split('-');
|
||||||
|
if ((permissionLocale === '*' || permissionLocale === locale) && (permissionArea === '*' || permissionArea === area)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -2467,15 +2467,10 @@ caniuse-api@^3.0.0:
|
||||||
lodash.memoize "^4.1.2"
|
lodash.memoize "^4.1.2"
|
||||||
lodash.uniq "^4.5.0"
|
lodash.uniq "^4.5.0"
|
||||||
|
|
||||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001093, caniuse-lite@^1.0.30001097:
|
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001093, caniuse-lite@^1.0.30001097, caniuse-lite@^1.0.30001148:
|
||||||
version "1.0.30001102"
|
version "1.0.30001171"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001102.tgz#3275e7a8d09548f955f665e532df88de0b63741a"
|
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001171.tgz"
|
||||||
integrity sha512-fOjqRmHjRXv1H1YD6QVLb96iKqnu17TjcLSaX64TwhGYed0P1E1CCWZ9OujbbK4Z/7zax7zAzvQidzdtjx8RcA==
|
integrity sha512-5Alrh8TTYPG9IH4UkRqEBZoEToWRLvPbSQokvzSz0lii8/FOWKG4keO1HoYfPWs8IF/NH/dyNPg1cmJGvV3Zlg==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001148:
|
|
||||||
version "1.0.30001151"
|
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001151.tgz#1ddfde5e6fff02aad7940b4edb7d3ac76b0cb00b"
|
|
||||||
integrity sha512-Zh3sHqskX6mHNrqUerh+fkf0N72cMxrmflzje/JyVImfpknscMnkeJrlFGJcqTmaa0iszdYptGpWMJCRQDkBVw==
|
|
||||||
|
|
||||||
canvas@^2.6.1:
|
canvas@^2.6.1:
|
||||||
version "2.6.1"
|
version "2.6.1"
|
||||||
|
|
Reference in New Issue