Browse Source

move from role check

avoid authStore.userData?.role === roles.ADMIN
pull/1660/head
Bernd Storath 6 months ago
parent
commit
190cfcfb8a
  1. 7
      src/app/components/ui/UserMenu.vue
  2. 2
      src/app/middleware/auth.global.ts
  3. 1
      src/server/api/session.get.ts
  4. 32
      src/shared/utils/permissions.ts

7
src/app/components/ui/UserMenu.vue

@ -37,7 +37,12 @@
Account Account
</NuxtLink> </NuxtLink>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem v-if="authStore.userData?.role === roles.ADMIN"> <DropdownMenuItem
v-if="
authStore.userData &&
hasPermissions(authStore.userData, 'admin', 'any')
"
>
<NuxtLink <NuxtLink
to="/admin" to="/admin"
class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"

2
src/app/middleware/auth.global.ts

@ -21,7 +21,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
// Check for admin access // Check for admin access
if (to.path.startsWith('/admin')) { if (to.path.startsWith('/admin')) {
if (userData.role !== roles.ADMIN) { if (userData && hasPermissions(userData, 'admin', 'any')) {
return abortNavigation('Not allowed to access Admin Panel'); return abortNavigation('Not allowed to access Admin Panel');
} }
} }

1
src/server/api/session.get.ts

@ -16,6 +16,7 @@ export default defineEventHandler(async (event) => {
} }
return { return {
id: user.id,
role: user.role, role: user.role,
username: user.username, username: user.username,
name: user.name, name: user.name,

32
src/shared/utils/permissions.ts

@ -1,7 +1,11 @@
import type { ClientType } from '#db/repositories/client/types'; import type { ClientType } from '#db/repositories/client/types';
import type { UserType } from '#db/repositories/user/types'; import type { UserType } from '#db/repositories/user/types';
export type Role = number & { readonly __role: unique symbol }; type BrandedRole = {
readonly __role: unique symbol;
};
export type Role = number & BrandedRole;
export const roles = { export const roles = {
ADMIN: 1 as Role, ADMIN: 1 as Role,
@ -26,9 +30,24 @@ function roleToKey(role: Role) {
return roleKey as Roles; return roleKey as Roles;
} }
// TODO: https://github.com/nitrojs/nitro/issues/2758#issuecomment-2650531472
type BrandedNumber = {
toString: unknown;
toFixed: unknown;
toExponential: unknown;
toPrecision: unknown;
valueOf: unknown;
toLocaleString: unknown;
} & BrandedRole;
type SharedUserType =
| Pick<UserType, 'id' | 'role'>
| (Pick<UserType, 'id'> & { role: BrandedNumber });
type PermissionCheck<Key extends keyof Permissions> = type PermissionCheck<Key extends keyof Permissions> =
| boolean | boolean
| ((user: UserType, data: Permissions[Key]['dataType']) => boolean); | ((user: SharedUserType, data: Permissions[Key]['dataType']) => boolean);
type RolesWithPermissions = { type RolesWithPermissions = {
[R in Roles]: { [R in Roles]: {
@ -87,14 +106,15 @@ export const ROLES = {
} as const satisfies RolesWithPermissions; } as const satisfies RolesWithPermissions;
export function hasPermissions<Resource extends keyof Permissions>( export function hasPermissions<Resource extends keyof Permissions>(
user: UserType, user: SharedUserType,
resource: Resource, resource: Resource,
action: Permissions[Resource]['action'], action: Permissions[Resource]['action'],
data?: Permissions[Resource]['dataType'] data?: Permissions[Resource]['dataType']
) { ) {
const permission = (ROLES as RolesWithPermissions)[roleToKey(user.role)][ const permission = (ROLES as RolesWithPermissions)[
resource // TODO: remove typecast
][action]; roleToKey(user.role as Role)
][resource][action];
if (typeof permission === 'boolean') { if (typeof permission === 'boolean') {
return permission; return permission;

Loading…
Cancel
Save