diff --git a/components/Roles.vue b/components/Roles.vue index 16ce6d381..2ce58359e 100644 --- a/components/Roles.vue +++ b/components/Roles.vue @@ -7,7 +7,7 @@ @@ -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: { diff --git a/pages/admin/index.vue b/pages/admin/index.vue index 2ab0b6cfb..4aed5e41b 100644 --- a/pages/admin/index.vue +++ b/pages/admin/index.vue @@ -130,7 +130,7 @@ const impersonate = async (email: string) => { ]" /> { ]" /> @@ -171,7 +171,7 @@ const impersonate = async (email: string) => { { { } 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) diff --git a/server/api/admin/users.get.ts b/server/api/admin/users.get.ts index bbecd9440..f54cd6dfb 100644 --- a/server/api/admin/users.get.ts +++ b/server/api/admin/users.get.ts @@ -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``; diff --git a/server/global.d.ts b/server/global.d.ts index 245949de9..8189639de 100644 --- a/server/global.d.ts +++ b/server/global.d.ts @@ -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 = (...args: Args) => Generator; @@ -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; db: Database; } diff --git a/server/notify.js b/server/notify.js index c51193374..4a700aa34 100644 --- a/server/notify.js +++ b/server/notify.js @@ -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; diff --git a/server/utils/useAuthentication.ts b/server/utils/useAuthentication.ts index 9eb55e080..943f42ddb 100644 --- a/server/utils/useAuthentication.ts +++ b/server/utils/useAuthentication.ts @@ -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 }; diff --git a/src/helpers.ts b/src/helpers.ts index c663b37ff..02ac17bfe 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -251,9 +251,31 @@ export const randomItemWeighted = (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, requestedLocale: string | null, requestedArea: string = ''): boolean => { +export type PermissionArea = typeof PERMISSION_AREAS[number] | ''; + +export const isGrantedForUser = (user: Pick, 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; -export const findAdmins = async (db: Database, locale: string, area: string): Promise => { - const admins = await db.all('SELECT username, email, roles, adminNotifications FROM users WHERE roles != \'\''); +export const findAdmins = async (db: Database, locale: string, area: PermissionArea): Promise => { + const admins = await db.all('SELECT username, email, roles, adminNotifications FROM users WHERE (roles != \'\' AND roles != \'*-external\')'); return admins.filter((admin) => isGrantedForUser(admin, locale, area)); }; diff --git a/test/helpers.test.ts b/test/helpers.test.ts index 1c612d4cc..416232cea 100644 --- a/test/helpers.test.ts +++ b/test/helpers.test.ts @@ -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, ) {} }