feat: throw 401 errors when a user doesn't have permissions (#5984)
* feat: throw 401 errors when a user doesn't have permissions * remove pointless message * prepr
This commit is contained in:
@@ -3,47 +3,49 @@
|
||||
<Teleport v-if="flags.projectBackground" to="#fixed-background-teleport">
|
||||
<ProjectBackgroundGradient :project="project" />
|
||||
</Teleport>
|
||||
<div v-if="route.name.startsWith('type-id-settings')" class="normal-page no-sidebar">
|
||||
<div class="normal-page__header">
|
||||
<div
|
||||
class="mb-4 flex flex-wrap items-center gap-x-2 gap-y-3 border-0 border-b-[1px] border-solid border-divider pb-4 text-lg font-semibold"
|
||||
>
|
||||
<nuxt-link
|
||||
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}`"
|
||||
class="flex items-center gap-2 hover:underline hover:brightness-[--hover-brightness]"
|
||||
<template v-if="isSettings">
|
||||
<div v-if="canAccessSettings" class="normal-page no-sidebar">
|
||||
<div class="normal-page__header">
|
||||
<div
|
||||
class="mb-4 flex flex-wrap items-center gap-x-2 gap-y-3 border-0 border-b-[1px] border-solid border-divider pb-4 text-lg font-semibold"
|
||||
>
|
||||
<Avatar :src="project.icon_url" size="32px" />
|
||||
{{ project.title }}
|
||||
</nuxt-link>
|
||||
<ChevronRightIcon />
|
||||
<span class="flex grow font-extrabold text-contrast">{{
|
||||
formatMessage(messages.settingsTitle)
|
||||
}}</span>
|
||||
<div class="flex gap-2">
|
||||
<ButtonStyled>
|
||||
<nuxt-link to="/dashboard/projects"
|
||||
><ListIcon /> {{ formatMessage(messages.visitProjectsDashboard) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<nuxt-link
|
||||
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}`"
|
||||
class="flex items-center gap-2 hover:underline hover:brightness-[--hover-brightness]"
|
||||
>
|
||||
<Avatar :src="project.icon_url" size="32px" />
|
||||
{{ project.title }}
|
||||
</nuxt-link>
|
||||
<ChevronRightIcon />
|
||||
<span class="flex grow font-extrabold text-contrast">{{
|
||||
formatMessage(messages.settingsTitle)
|
||||
}}</span>
|
||||
<div class="flex gap-2">
|
||||
<ButtonStyled>
|
||||
<nuxt-link to="/dashboard/projects"
|
||||
><ListIcon /> {{ formatMessage(messages.visitProjectsDashboard) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<ProjectMemberHeader
|
||||
v-if="currentMember && false"
|
||||
:project="project"
|
||||
:versions="versions"
|
||||
:current-member="currentMember"
|
||||
:is-settings="isSettings"
|
||||
:set-processing="setProcessing"
|
||||
:all-members="allMembers"
|
||||
:update-members="invalidateProject"
|
||||
:auth="auth"
|
||||
:tags="tags"
|
||||
/>
|
||||
</div>
|
||||
<div class="normal-page__content">
|
||||
<NuxtPage />
|
||||
</div>
|
||||
<ProjectMemberHeader
|
||||
v-if="currentMember && false"
|
||||
:project="project"
|
||||
:versions="versions"
|
||||
:current-member="currentMember"
|
||||
:is-settings="route.name.startsWith('type-id-settings')"
|
||||
:set-processing="setProcessing"
|
||||
:all-members="allMembers"
|
||||
:update-members="invalidateProject"
|
||||
:auth="auth"
|
||||
:tags="tags"
|
||||
/>
|
||||
</div>
|
||||
<div class="normal-page__content">
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-else>
|
||||
<NewModal
|
||||
@@ -811,7 +813,7 @@
|
||||
:project="project"
|
||||
:versions="versions"
|
||||
:current-member="currentMember"
|
||||
:is-settings="route.name.startsWith('type-id-settings')"
|
||||
:is-settings="isSettings"
|
||||
:route-name="route.name"
|
||||
:set-processing="setProcessing"
|
||||
:collapsed="collapsedChecklist"
|
||||
@@ -1826,6 +1828,8 @@ const { data: organizationRaw } = useQuery({
|
||||
// Return null when the project no longer belongs to an organization.
|
||||
const organization = computed(() => (projectRaw.value?.organization ? organizationRaw.value : null))
|
||||
|
||||
const isSettings = computed(() => route.name.startsWith('type-id-settings'))
|
||||
|
||||
// Transform versionsV3 to be same shape as versionsV2 for compatibility in project pages
|
||||
const versionsRaw = computed(() => {
|
||||
return (versionsV3.value ?? []).map((v) => {
|
||||
@@ -2262,11 +2266,27 @@ const currentMember = computed(() => {
|
||||
return val
|
||||
})
|
||||
|
||||
const canAccessSettings = computed(() => !!currentMember.value?.accepted)
|
||||
|
||||
const hasEditDetailsPermission = computed(() => {
|
||||
const EDIT_DETAILS = 1 << 2
|
||||
return (currentMember.value?.permissions & EDIT_DETAILS) === EDIT_DETAILS
|
||||
})
|
||||
|
||||
watch(
|
||||
[isSettings, currentMember],
|
||||
() => {
|
||||
if (isSettings.value && !canAccessSettings.value) {
|
||||
showError({
|
||||
fatal: true,
|
||||
statusCode: 401,
|
||||
statusMessage: 'Unauthorized',
|
||||
})
|
||||
}
|
||||
},
|
||||
{ flush: 'sync', immediate: true },
|
||||
)
|
||||
|
||||
const projectTypeDisplay = computed(() => {
|
||||
if (!project.value) return ''
|
||||
return formatProjectType(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="canAccess">
|
||||
<section class="universal-card">
|
||||
<h2>Project status</h2>
|
||||
<Badge :type="project.status" />
|
||||
@@ -107,7 +107,7 @@ import {
|
||||
injectProjectPageContext,
|
||||
} from '@modrinth/ui'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed } from 'vue'
|
||||
import { computed, watch } from 'vue'
|
||||
|
||||
import ConversationThread from '~/components/ui/thread/ConversationThread.vue'
|
||||
import {
|
||||
@@ -122,6 +122,22 @@ import {
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { projectV2: project, currentMember, invalidate } = injectProjectPageContext()
|
||||
|
||||
const canAccess = computed(() => !!currentMember.value)
|
||||
|
||||
watch(
|
||||
[currentMember, project],
|
||||
() => {
|
||||
if (project.value && !canAccess.value) {
|
||||
showError({
|
||||
fatal: true,
|
||||
statusCode: 401,
|
||||
statusMessage: 'Unauthorized',
|
||||
})
|
||||
}
|
||||
},
|
||||
{ flush: 'sync', immediate: true },
|
||||
)
|
||||
|
||||
const auth = await useAuth()
|
||||
const client = injectModrinthClient()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: ['auth', 'staff'],
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
robots: 'noindex',
|
||||
})
|
||||
|
||||
@@ -19,7 +19,7 @@ import { FolderIcon, ReportIcon, ShieldCheckIcon } from '@modrinth/assets'
|
||||
import { Chips, defineMessages, NavTabs, useVIntl } from '@modrinth/ui'
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'auth',
|
||||
middleware: ['auth', 'staff'],
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
|
||||
@@ -9,54 +9,56 @@
|
||||
>
|
||||
<ModalCreation ref="modal_creation" :organization-id="organization.id" />
|
||||
<template v-if="routeHasSettings">
|
||||
<div class="normal-page__sidebar">
|
||||
<div
|
||||
class="bg-surface mb-4 flex flex-col rounded-xl border border-solid border-surface-4 p-4"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<Avatar size="sm" :src="organization.icon_url" />
|
||||
<div class="flex flex-col justify-center gap-1">
|
||||
<h2 class="m-0 text-base">
|
||||
<nuxt-link :to="`/organization/${organization.slug}/settings`">
|
||||
{{ organization.name }}
|
||||
</nuxt-link>
|
||||
</h2>
|
||||
<span>
|
||||
{{ formatCompactNumber(acceptedMembers?.length || 0) }}
|
||||
member<template v-if="acceptedMembers?.length !== 1">s</template>
|
||||
</span>
|
||||
<template v-if="canAccessSettings">
|
||||
<div class="normal-page__sidebar">
|
||||
<div
|
||||
class="bg-surface mb-4 flex flex-col rounded-xl border border-solid border-surface-4 p-4"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<Avatar size="sm" :src="organization.icon_url" />
|
||||
<div class="flex flex-col justify-center gap-1">
|
||||
<h2 class="m-0 text-base">
|
||||
<nuxt-link :to="`/organization/${organization.slug}/settings`">
|
||||
{{ organization.name }}
|
||||
</nuxt-link>
|
||||
</h2>
|
||||
<span>
|
||||
{{ formatCompactNumber(acceptedMembers?.length || 0) }}
|
||||
member<template v-if="acceptedMembers?.length !== 1">s</template>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NavStack
|
||||
:items="[
|
||||
{
|
||||
link: `/organization/${organization.slug}/settings`,
|
||||
label: 'Overview',
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
{
|
||||
link: `/organization/${organization.slug}/settings/members`,
|
||||
label: 'Members',
|
||||
icon: UsersIcon,
|
||||
},
|
||||
{
|
||||
link: `/organization/${organization.slug}/settings/projects`,
|
||||
label: 'Projects',
|
||||
icon: BoxIcon,
|
||||
},
|
||||
{
|
||||
link: `/organization/${organization.slug}/settings/analytics`,
|
||||
label: 'Analytics',
|
||||
icon: ChartIcon,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
<div class="normal-page__content">
|
||||
<NuxtPage />
|
||||
</div>
|
||||
<NavStack
|
||||
:items="[
|
||||
{
|
||||
link: `/organization/${organization.slug}/settings`,
|
||||
label: 'Overview',
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
{
|
||||
link: `/organization/${organization.slug}/settings/members`,
|
||||
label: 'Members',
|
||||
icon: UsersIcon,
|
||||
},
|
||||
{
|
||||
link: `/organization/${organization.slug}/settings/projects`,
|
||||
label: 'Projects',
|
||||
icon: BoxIcon,
|
||||
},
|
||||
{
|
||||
link: `/organization/${organization.slug}/settings/analytics`,
|
||||
label: 'Analytics',
|
||||
icon: ChartIcon,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
<div class="normal-page__content">
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="normal-page__header py-4">
|
||||
@@ -527,6 +529,22 @@ const { currentMember } = organizationContext
|
||||
|
||||
provideOrganizationContext(organizationContext)
|
||||
|
||||
const canAccessSettings = computed(() => !!currentMember.value?.accepted)
|
||||
|
||||
watch(
|
||||
[routeHasSettings, currentMember],
|
||||
() => {
|
||||
if (routeHasSettings.value && !canAccessSettings.value) {
|
||||
showError({
|
||||
fatal: true,
|
||||
statusCode: 401,
|
||||
statusMessage: 'Unauthorized',
|
||||
})
|
||||
}
|
||||
},
|
||||
{ flush: 'sync', immediate: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
organization,
|
||||
(org) => {
|
||||
|
||||
Reference in New Issue
Block a user