feat: owner read-only view-as from team users table
Allow org owners to inspect any member's data scope via dual JWT; server blocks mutations and team page stays reachable for return. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -26,7 +26,15 @@ const route = useRoute();
|
||||
const { unreadTotal: chatUnreadTotal, ensureConnected: ensureChatConnected } = useOrgChat();
|
||||
const chatBadge = computed(() => chatUnreadTotal.value);
|
||||
|
||||
const canOpenTeamWhileViewing = computed(() =>
|
||||
Boolean(auth.user?.viewAs?.canOpenTeamSettings),
|
||||
);
|
||||
/** تنظیمات مدیریتی واقعی (نقش مشاهدهشده) */
|
||||
const canSettings = computed(() => canManageSettings(auth.user?.role));
|
||||
/** نمایش هاب تنظیمات؛ در مشاهده فقطخواندنی فقط صفحهٔ تیم باز میماند */
|
||||
const showSettingsNav = computed(
|
||||
() => canSettings.value || canOpenTeamWhileViewing.value,
|
||||
);
|
||||
const showOwnRecordsBanner = computed(
|
||||
() => Boolean(auth.user?.role) && !canViewAllRecords(auth.user?.role),
|
||||
);
|
||||
@@ -142,15 +150,30 @@ function moduleAllowed(module?: string) {
|
||||
return enabledModules.value.includes(module as never);
|
||||
}
|
||||
|
||||
function cardAllowedForNav(card: {
|
||||
ownerOnly?: boolean;
|
||||
admin?: boolean;
|
||||
module?: string;
|
||||
to?: string;
|
||||
}) {
|
||||
if (card.ownerOnly && auth.user?.role !== 'owner') return false;
|
||||
if (card.admin && !canSettings.value && !canOpenTeamWhileViewing.value) return false;
|
||||
if (!moduleAllowed(card.module)) return false;
|
||||
if (
|
||||
canOpenTeamWhileViewing.value &&
|
||||
!canSettings.value &&
|
||||
card.to &&
|
||||
!card.to.startsWith('/settings/team')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const hubVisible = computed(() => {
|
||||
const map: Record<string, boolean> = {};
|
||||
for (const hub of NAV_HUBS) {
|
||||
map[hub.key] = hub.cards.some(
|
||||
(card) =>
|
||||
(!card.ownerOnly || auth.user?.role === 'owner') &&
|
||||
(!card.admin || canSettings.value) &&
|
||||
moduleAllowed(card.module),
|
||||
);
|
||||
map[hub.key] = hub.cards.some((card) => cardAllowedForNav(card));
|
||||
}
|
||||
return map;
|
||||
});
|
||||
@@ -159,12 +182,7 @@ const openHub = computed<NavHubDef | null>(() => {
|
||||
if (!openHubKey.value) return null;
|
||||
const hub = NAV_HUBS.find((item) => item.key === openHubKey.value);
|
||||
if (!hub) return null;
|
||||
const cards = hub.cards.filter(
|
||||
(card) =>
|
||||
(!card.ownerOnly || auth.user?.role === 'owner') &&
|
||||
(!card.admin || canSettings.value) &&
|
||||
moduleAllowed(card.module),
|
||||
);
|
||||
const cards = hub.cards.filter((card) => cardAllowedForNav(card));
|
||||
if (
|
||||
hub.key === 'customers' &&
|
||||
isMobileNav.value &&
|
||||
@@ -183,10 +201,7 @@ const openHub = computed<NavHubDef | null>(() => {
|
||||
return {
|
||||
...hub,
|
||||
cards,
|
||||
shortcuts: hub.shortcuts.filter(
|
||||
(item) =>
|
||||
(!item.admin || canSettings.value) && moduleAllowed(item.module),
|
||||
),
|
||||
shortcuts: hub.shortcuts.filter((item) => cardAllowedForNav(item)),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -657,7 +672,7 @@ onUnmounted(() => {
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="canSettings"
|
||||
v-if="showSettingsNav"
|
||||
class="nav-fold"
|
||||
:class="{ active: settingsActive || openHubKey === 'settings' }"
|
||||
>
|
||||
@@ -1027,7 +1042,7 @@ onUnmounted(() => {
|
||||
<i class="chev" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
v-if="canSettings"
|
||||
v-if="showSettingsNav"
|
||||
class="nav-item nav-fold-btn"
|
||||
type="button"
|
||||
:class="{
|
||||
|
||||
Reference in New Issue
Block a user