#132 fine-grained permissions
This commit is contained in:
parent
2a31d688ba
commit
257db4099e
|
@ -26,7 +26,7 @@
|
|||
</a>
|
||||
</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>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Loading :value="nounsRaw">
|
||||
<section v-if="$admin()" class="px-3">
|
||||
<section v-if="$isGranted('nouns')" class="px-3">
|
||||
<div class="alert alert-info">
|
||||
<strong>{{ nounsCountApproved() }}</strong> <T>nouns.approved</T>,
|
||||
<strong>{{ nounsCountPending() }}</strong> <T>nouns.pending</T>.
|
||||
|
@ -142,7 +142,7 @@
|
|||
</nuxt-link>
|
||||
</li>
|
||||
-->
|
||||
<template v-if="$admin()">
|
||||
<template v-if="$isGranted('nouns')">
|
||||
<li v-if="!s.el.approved">
|
||||
<button class="btn btn-concise btn-success btn-sm m-1" @click="approve(s.el)">
|
||||
<Icon v="check"/>
|
||||
|
@ -166,7 +166,7 @@
|
|||
<button class="btn btn-concise btn-outline-primary btn-sm m-1" @click="edit(s.el)">
|
||||
<Icon v="pen"/>
|
||||
<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>
|
||||
</span>
|
||||
</button>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Loading :value="entriesRaw">
|
||||
<section v-if="$admin()" class="px-3">
|
||||
<section v-if="$isGranted('inclusive')" class="px-3">
|
||||
<div class="alert alert-info">
|
||||
<strong>{{ entriesCountApproved() }}</strong> <T>nouns.approved</T>,
|
||||
<strong>{{ entriesCountPending() }}</strong> <T>nouns.pending</T>.
|
||||
|
@ -126,7 +126,7 @@
|
|||
</nuxt-link>
|
||||
</li>
|
||||
-->
|
||||
<template v-if="$admin()">
|
||||
<template v-if="$isGranted('inclusive')">
|
||||
<li v-if="!s.el.approved">
|
||||
<button class="btn btn-concise btn-success btn-sm m-1" @click="approve(s.el)">
|
||||
<Icon v="check"/>
|
||||
|
@ -150,7 +150,7 @@
|
|||
<button class="btn btn-concise btn-outline-primary btn-sm m-1" @click="edit(s.el)">
|
||||
<Icon v="pen"/>
|
||||
<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>
|
||||
</span>
|
||||
</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">
|
||||
<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>
|
||||
<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">
|
||||
<span class="badge badge-danger">
|
||||
<Icon v="map-marker-question"/>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Loading :value="entriesRaw">
|
||||
<section v-if="$admin()" class="px-3">
|
||||
<section v-if="$isGranted('terms')" class="px-3">
|
||||
<div class="alert alert-info">
|
||||
<strong>{{ entriesCountApproved() }}</strong> <T>nouns.approved</T>,
|
||||
<strong>{{ entriesCountPending() }}</strong> <T>nouns.pending</T>.
|
||||
|
@ -70,7 +70,7 @@
|
|||
</nuxt-link>
|
||||
</li>
|
||||
-->
|
||||
<template v-if="$admin()">
|
||||
<template v-if="$$isGranted('terms')">
|
||||
<li v-if="!s.el.approved">
|
||||
<button class="btn btn-concise btn-success btn-sm m-1" @click="approve(s.el)">
|
||||
<Icon v="check"/>
|
||||
|
@ -94,7 +94,7 @@
|
|||
<button class="btn btn-concise btn-outline-primary btn-sm m-1" @click="edit(s.el)">
|
||||
<Icon v="pen"/>
|
||||
<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>
|
||||
</span>
|
||||
</button>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<textarea v-model="form.definition" class="form-control form-control-sm" required rows="3"></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="$admin()">
|
||||
<tr v-if="$isGranted('terms')">
|
||||
<td colspan="3">
|
||||
<T>profile.flags</T>
|
||||
<ListInput v-model="form.flags" v-slot="s"/>
|
||||
|
|
|
@ -429,7 +429,7 @@ admin:
|
|||
user:
|
||||
user: 'User'
|
||||
email: 'Email'
|
||||
roles: 'Role'
|
||||
roles: 'Permissions'
|
||||
profiles: 'Profiles'
|
||||
confirmRole: 'Are you sure you want to switch @%username%''s role to "%role%"?'
|
||||
|
||||
|
|
|
@ -990,7 +990,7 @@ admin:
|
|||
user:
|
||||
user: 'Użytkownicze'
|
||||
email: 'Email'
|
||||
roles: 'Rola'
|
||||
roles: 'Uprawnienia'
|
||||
profiles: 'Profile'
|
||||
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 t from "../src/translator";
|
||||
import config from '../data/config.suml';
|
||||
import {isGranted} from "../src/helpers";
|
||||
|
||||
export default ({app, store}) => {
|
||||
const token = app.$cookies.get('token');
|
||||
|
@ -11,7 +12,7 @@ export default ({app, store}) => {
|
|||
}
|
||||
|
||||
Vue.prototype.$user = _ => store.state.user;
|
||||
Vue.prototype.$admin = _ => {
|
||||
return store.state.user && store.state.user.authenticated && store.state.user.roles === 'admin';
|
||||
};
|
||||
Vue.prototype.$isGranted = (area) => {
|
||||
return store.state.user && store.state.user.authenticated && isGranted(store.state.user, config.locale, area);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,34 @@
|
|||
<template>
|
||||
<NotFound v-if="!$admin()"/>
|
||||
<NotFound v-if="!$isGranted('panel') && !$isGranted('users')"/>
|
||||
<div v-else>
|
||||
<h2>
|
||||
<Icon v="user-cog"/>
|
||||
<T>admin.header</T>
|
||||
</h2>
|
||||
|
||||
<section>
|
||||
<section v-if="$isGranted('users')">
|
||||
<details class="border mb-3">
|
||||
<summary class="bg-light p-3">
|
||||
<Icon v="users"/>
|
||||
Users
|
||||
({{stats.users.overall}}, {{stats.users.admins}} admins)
|
||||
({{stats.users.overall}} overall, {{stats.users.admins}} admins, {{visibleUsers.length}} visible)
|
||||
</summary>
|
||||
<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">
|
||||
<template v-slot:header>
|
||||
<th class="text-nowrap">
|
||||
|
@ -49,10 +63,7 @@
|
|||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" :class="['badge', s.el.roles === 'admin' ? 'badge-primary' : 'badge-light']"
|
||||
@click.prevent="setRole(s.el.id, s.el.roles === 'admin' ? 'user' : 'admin')">
|
||||
{{s.el.roles}}
|
||||
</a>
|
||||
<Roles :user="s.el"/>
|
||||
</td>
|
||||
<td>
|
||||
<ul class="list-unstyled">
|
||||
|
@ -117,7 +128,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {head} from "../src/helpers";
|
||||
import {head, isGranted} from "../src/helpers";
|
||||
import {socialProviders} from "../src/data";
|
||||
|
||||
export default {
|
||||
|
@ -125,34 +136,34 @@
|
|||
return {
|
||||
socialProviders,
|
||||
userFilter: '',
|
||||
localeFilter: true,
|
||||
adminsFilter: false,
|
||||
}
|
||||
},
|
||||
async asyncData({ app, store }) {
|
||||
if (!store.state.user || store.state.user.roles !== 'admin') {
|
||||
return {};
|
||||
}
|
||||
let stats = { users: {}};
|
||||
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 {
|
||||
stats,
|
||||
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: {
|
||||
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() {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</h2>
|
||||
|
||||
<template v-if="q === null">
|
||||
<section v-if="$admin()">
|
||||
<section v-if="$isGranted('census')">
|
||||
<div class="alert alert-info">
|
||||
{{countResponses}}
|
||||
<T>census.replies</T>
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
</ul>
|
||||
</section>
|
||||
|
||||
<section v-if="$admin()" class="px-3">
|
||||
<section v-if="$isGranted('sources')" class="px-3">
|
||||
<div class="alert alert-info">
|
||||
<strong>{{ sourceLibrary.countApproved }}</strong> <T>nouns.approved</T>,
|
||||
<a href="#" @click.prevent="filter='__awaiting__'">
|
||||
|
|
|
@ -6,7 +6,7 @@ import cookieParser from 'cookie-parser';
|
|||
import grant from "grant";
|
||||
import router from "./routes/user";
|
||||
import { loadSuml } from './loader';
|
||||
import {buildLocaleList} from "../src/helpers";
|
||||
import {buildLocaleList, isGranted} from "../src/helpers";
|
||||
|
||||
global.config = loadSuml('config');
|
||||
|
||||
|
@ -27,7 +27,7 @@ app.use(async function (req, res, next) {
|
|||
req.locales = buildLocaleList(global.config.locale);
|
||||
req.rawUser = authenticate(req);
|
||||
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();
|
||||
next();
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@ import {now, sortByValue} from "../../src/helpers";
|
|||
const router = Router();
|
||||
|
||||
router.get('/admin/users', async (req, res) => {
|
||||
if (!req.admin) {
|
||||
if (!req.isGranted('users')) {
|
||||
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
|
||||
FROM users u
|
||||
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`
|
||||
|
@ -47,18 +47,19 @@ router.get('/admin/users', async (req, res) => {
|
|||
});
|
||||
|
||||
router.get('/admin/stats', async (req, res) => {
|
||||
if (!req.admin) {
|
||||
if (!req.isGranted('panel')) {
|
||||
return res.status(401).json({error: 'Unauthorised'});
|
||||
}
|
||||
|
||||
const users = {
|
||||
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 = {};
|
||||
for (let locale in req.locales) {
|
||||
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 pronouns = {}
|
||||
const flags = {}
|
||||
|
|
|
@ -26,7 +26,7 @@ router.get('/inclusive', async (req, res) => {
|
|||
SELECT i.*, u.username AS author FROM inclusive i
|
||||
LEFT JOIN users u ON i.author_id = u.id
|
||||
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
|
||||
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
|
||||
LEFT JOIN users u ON i.author_id = u.id
|
||||
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.insteadOf like ${term} OR i.say like ${term})
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ router.post('/inclusive/submit', async (req, res) => {
|
|||
});
|
||||
|
||||
router.post('/inclusive/hide/:id', async (req, res) => {
|
||||
if (!req.admin) {
|
||||
if (!req.isGranted('inclusive')) {
|
||||
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) => {
|
||||
if (!req.admin) {
|
||||
if (!req.isGranted('inclusive')) {
|
||||
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) => {
|
||||
if (!req.admin) {
|
||||
if (!req.isGranted('inclusive')) {
|
||||
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
|
||||
WHERE n.locale = ${req.config.locale}
|
||||
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
|
||||
`));
|
||||
});
|
||||
|
@ -42,7 +42,7 @@ router.get('/nouns/search/:term', async (req, res) => {
|
|||
SELECT n.*, u.username AS author FROM nouns n
|
||||
LEFT JOIN users u ON n.author_id = u.id
|
||||
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.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
|
||||
|
@ -65,7 +65,7 @@ router.post('/nouns/submit', async (req, res) => {
|
|||
)
|
||||
`);
|
||||
|
||||
if (req.admin) {
|
||||
if (req.isGranted('nouns')) {
|
||||
await approve(req.db, id);
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@ router.post('/nouns/submit', async (req, res) => {
|
|||
});
|
||||
|
||||
router.post('/nouns/hide/:id', async (req, res) => {
|
||||
if (!req.admin) {
|
||||
if (!req.isGranted('nouns')) {
|
||||
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) => {
|
||||
if (!req.admin) {
|
||||
if (!req.isGranted('nouns')) {
|
||||
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) => {
|
||||
if (!req.admin) {
|
||||
if (!req.isGranted('nouns')) {
|
||||
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`
|
||||
SELECT * FROM nouns
|
||||
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})
|
||||
ORDER BY masc
|
||||
`)).filter(noun =>
|
||||
|
|
|
@ -26,7 +26,7 @@ router.get('/sources', async (req, res) => {
|
|||
LEFT JOIN users u ON s.submitter_id = u.id
|
||||
WHERE s.locale = ${req.config.locale}
|
||||
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
|
||||
WHERE s.locale = ${req.config.locale}
|
||||
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}
|
||||
`));
|
||||
});
|
||||
|
@ -53,7 +53,7 @@ router.post('/sources/submit', async (req, res) => {
|
|||
)
|
||||
`);
|
||||
|
||||
if (req.admin) {
|
||||
if (req.isGranted('sources')) {
|
||||
await approve(req.db, id);
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ router.post('/sources/submit', async (req, res) => {
|
|||
});
|
||||
|
||||
router.post('/sources/hide/:id', async (req, res) => {
|
||||
if (!req.admin) {
|
||||
if (!req.isGranted('sources')) {
|
||||
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) => {
|
||||
if (!req.admin) {
|
||||
if (!req.isGranted('sources')) {
|
||||
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) => {
|
||||
if (!req.admin) {
|
||||
if (!req.isGranted('sources')) {
|
||||
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
|
||||
LEFT JOIN users u ON i.author_id = u.id
|
||||
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
|
||||
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
|
||||
LEFT JOIN users u ON i.author_id = u.id
|
||||
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.term like ${term} OR i.original like ${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);
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ router.post('/terms/submit', async (req, res) => {
|
|||
});
|
||||
|
||||
router.post('/terms/hide/:id', async (req, res) => {
|
||||
if (!req.admin) {
|
||||
if (!req.isGranted('terms')) {
|
||||
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) => {
|
||||
if (!req.admin) {
|
||||
if (!req.isGranted('terms')) {
|
||||
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) => {
|
||||
if (!req.admin) {
|
||||
if (!req.isGranted('terms')) {
|
||||
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) => {
|
||||
if (!req.admin) {
|
||||
if (!req.isGranted('*')) {
|
||||
return res.status(401).json({error: 'Unauthorised'});
|
||||
}
|
||||
|
||||
|
|
|
@ -175,3 +175,21 @@ export const shuffle = 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.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001093, caniuse-lite@^1.0.30001097:
|
||||
version "1.0.30001102"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001102.tgz#3275e7a8d09548f955f665e532df88de0b63741a"
|
||||
integrity sha512-fOjqRmHjRXv1H1YD6QVLb96iKqnu17TjcLSaX64TwhGYed0P1E1CCWZ9OujbbK4Z/7zax7zAzvQidzdtjx8RcA==
|
||||
|
||||
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==
|
||||
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.30001171"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001171.tgz"
|
||||
integrity sha512-5Alrh8TTYPG9IH4UkRqEBZoEToWRLvPbSQokvzSz0lii8/FOWKG4keO1HoYfPWs8IF/NH/dyNPg1cmJGvV3Zlg==
|
||||
|
||||
canvas@^2.6.1:
|
||||
version "2.6.1"
|
||||
|
|
Reference in New Issue