implement CR feedback

This commit is contained in:
Andrea Vos 2025-05-03 18:11:56 +02:00
parent 8f74d83a43
commit 9682965918
9 changed files with 48 additions and 36 deletions

View File

@ -7,7 +7,7 @@
</option>
</select>
<select v-model="s.val.area" class="form-control">
<option v-for="a in allAreas" :value="a">
<option v-for="a in PERMISSION_AREAS" :value="a">
{{ a }}
</option>
</select>
@ -28,6 +28,8 @@ import useConfig from '../composables/useConfig.ts';
import useDialogue from '../composables/useDialogue.ts';
import allLocalesRaw from '../locale/locales.ts';
import { PERMISSION_AREAS } from '~/src/helpers.js';
const allLocales = ['*'];
for (const { code } of allLocalesRaw) {
allLocales.push(code);
@ -53,25 +55,7 @@ export default {
return { locale, area };
}),
allLocales,
allAreas: [
'*',
'basic',
'panel',
'users',
'sources',
'nouns',
'terms',
'inclusive',
'census',
'names',
'translations',
'code',
'org',
'impersonate',
'community',
'blog',
'external',
],
PERMISSION_AREAS,
};
},
methods: {

View File

@ -130,7 +130,7 @@ const impersonate = async (email: string) => {
]"
/>
<AdminDashboardCard
v-show="!filterAttention && $isGranted('')"
v-show="!filterAttention && $isGranted('panel')"
icon="user-cog"
link="/admin/moderation"
header="Moderation rules"
@ -154,7 +154,7 @@ const impersonate = async (email: string) => {
]"
/>
<AdminDashboardCard
v-show="!filterAttention && $isGranted('')"
v-show="!filterAttention && $isGranted('panel')"
icon="bell"
header="Email notifications"
>
@ -171,7 +171,7 @@ const impersonate = async (email: string) => {
</span>
</AdminDashboardCard>
<AdminDashboardCard
v-show="!filterAttention && $isGranted('')"
v-show="!filterAttention"
v-if="$isGranted('users') || $isGranted('community')"
icon="user-secret"
header="@example"
@ -181,19 +181,19 @@ const impersonate = async (email: string) => {
</button>
</AdminDashboardCard>
<AdminDashboardCard
v-show="!filterAttention && $isGranted('')"
v-show="!filterAttention && $isGranted('panel')"
icon="b:discord"
link="https://team-discord.pronouns.page"
header="Team Discord"
/>
<AdminDashboardCard
v-show="!filterAttention && $isGranted('')"
v-show="!filterAttention && $isGranted('panel')"
icon="palette"
link="/design"
header="Design guidelines"
/>
<AdminDashboardCard
v-show="!filterAttention && ($isGranted('') || $isGranted('external'))"
v-show="!filterAttention && ($isGranted('panel') || $isGranted('external'))"
icon="file-spreadsheet"
link="/admin/timesheets"
header="Volunteering timesheets"

View File

@ -7,6 +7,8 @@ import { isGrantedForUser, parseUserJwt } from '../src/helpers.ts';
import type { Account, User } from '../src/user.ts';
import { useMainStore } from '../store/index.ts';
import type { PermissionArea } from '~/src/helpers.ts';
declare module '#app' {
interface NuxtApp {
$user(): User | null;
@ -41,7 +43,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
}
const user = () => store.user;
const isGranted = (area = '', locale = null): boolean => {
const isGranted = (area: PermissionArea = '', locale = null): boolean => {
return !!store.user &&
!!store.user.authenticated &&
isGrantedForUser(store.user, locale || config.locale, area)

View File

@ -31,7 +31,7 @@ export default defineEventHandler(async (event) => {
conditions.push(SQL`p.locale=${locale}`);
}
if (query.adminsFilter) {
conditions.push(SQL`u.roles != ''`);
conditions.push(SQL`(u.roles != '' AND u.roles != '*-external')`);
}
let conditionsSql = SQL``;

3
server/global.d.ts vendored
View File

@ -2,6 +2,7 @@ import type { LocaleDescription } from '../locale/locales.ts';
import type { User } from '../src/user.ts';
import type { Database } from './db.ts';
import type { PermissionArea } from '~/src/helpers.ts';
declare global {
type GeneratorFunc<R, Args extends unknown[]> = (...args: Args) => Generator<R>;
@ -10,7 +11,7 @@ declare global {
export interface Request {
rawUser: User | undefined;
user: User | null;
isGranted: (area?: string, locale?: string) => boolean;
isGranted: (area?: PermissionArea, locale?: string) => boolean;
locales: Record<string, LocaleDescription>;
db: Database;
}

View File

@ -37,7 +37,7 @@ async function notify() {
return;
}
const admins = await db.all('SELECT email, roles, adminNotifications FROM users WHERE roles != \'\'');
const admins = await db.all('SELECT email, roles, adminNotifications FROM users WHERE (roles != \'\' and roles != \'*-external\')');
const awaitingModerationGrouped = {};
let count = 0;

View File

@ -3,9 +3,10 @@ import type { H3Event } from 'h3';
import { getLocale } from '~/server/data.ts';
import jwt from '~/server/jwt.ts';
import { isGrantedForUser } from '~/src/helpers.ts';
import type { PermissionArea } from '~/src/helpers.ts';
import type { User } from '~/src/user.ts';
export type IsGrantedFn = (area?: string, locale?: string) => boolean;
export type IsGrantedFn = (area?: PermissionArea, locale?: string) => boolean;
export default async (event: H3Event) => {
let rawUser = undefined;
@ -22,7 +23,7 @@ export default async (event: H3Event) => {
const user = rawUser?.authenticated ? rawUser : null;
const requestedLocale = getLocale(event);
const isGranted = (area: string = '', locale = requestedLocale): boolean => {
const isGranted = (area: PermissionArea = '', locale = requestedLocale): boolean => {
return !!user && isGrantedForUser(user, locale, area);
};
return { rawUser, user, isGranted };

View File

@ -251,9 +251,31 @@ export const randomItemWeighted = <T extends WeightedItem>(array: T[]): T => {
export const randomNumber = (min: number, max: number): number => Math.floor(random() * (max - min + 1)) + min;
export const PERMISSION_AREAS = [
'*',
'basic',
'panel',
'users',
'sources',
'nouns',
'terms',
'inclusive',
'census',
'names',
'translations',
'code',
'org',
'impersonate',
'community',
'blog',
'external',
] as const;
const RESTRICTED_AREAS = ['code', 'org', 'impersonate', 'community'];
export const isGrantedForUser = (user: Pick<User, 'roles'>, requestedLocale: string | null, requestedArea: string = ''): boolean => {
export type PermissionArea = typeof PERMISSION_AREAS[number] | '';
export const isGrantedForUser = (user: Pick<User, 'roles'>, requestedLocale: string | null, requestedArea: PermissionArea = ''): boolean => {
if (requestedArea === '*') {
// only super-admin
return user.roles.split('|').includes('*');
@ -542,8 +564,8 @@ export const deepSet = (obj: object, path: string, value: unknown): void => {
type AdminUser = Pick<User, 'username' | 'email' | 'roles' | 'adminNotifications'>;
export const findAdmins = async (db: Database, locale: string, area: string): Promise<AdminUser[]> => {
const admins = await db.all<AdminUser>('SELECT username, email, roles, adminNotifications FROM users WHERE roles != \'\'');
export const findAdmins = async (db: Database, locale: string, area: PermissionArea): Promise<AdminUser[]> => {
const admins = await db.all<AdminUser>('SELECT username, email, roles, adminNotifications FROM users WHERE (roles != \'\' AND roles != \'*-external\')');
return admins.filter((admin) => isGrantedForUser(admin, locale, area));
};

View File

@ -9,6 +9,8 @@ import {
isGrantedForUser,
} from '../src/helpers.ts';
import type { PermissionArea } from '~/src/helpers.ts';
const controlSymbols = [
{
description: 'slashes',
@ -150,7 +152,7 @@ class GrantTest {
constructor(
public readonly givenRoles: string,
public readonly requestedLocale: string,
public readonly requestedArea: string,
public readonly requestedArea: PermissionArea,
public readonly granted: boolean,
) {}
}