UBERF-5812: Fix allow to delete based on all my accounts (#4823)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-03-01 23:39:46 +07:00 committed by GitHub
parent 5cd8dca583
commit ae6059c040
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 124 additions and 62 deletions

View File

@ -326,7 +326,7 @@ function defineApplication (
spaceClass: tracker.class.Project, spaceClass: tracker.class.Project,
componentProps: { componentProps: {
_class: tracker.class.Project, _class: tracker.class.Project,
label: tracker.string.AllIssues, label: tracker.string.AllProjects,
icon: tracker.icon.Issues icon: tracker.icon.Issues
} }
} }

View File

@ -187,12 +187,22 @@ export async function setClient (_client: MeasureClient): Promise<void> {
* @public * @public
*/ */
export async function refreshClient (): Promise<void> { export async function refreshClient (): Promise<void> {
await liveQuery?.refreshConnect() if (!(liveQuery?.isClosed() ?? true)) {
for (const q of globalQueries) { await liveQuery?.refreshConnect()
q.refreshClient() for (const q of globalQueries) {
q.refreshClient()
}
} }
} }
/**
* @public
*/
export async function purgeClient (): Promise<void> {
await liveQuery?.close()
await pipeline?.close()
}
/** /**
* @public * @public
*/ */
@ -529,13 +539,9 @@ export function isCollectionAttr (hierarchy: Hierarchy, key: KeyedAttribute): bo
* @public * @public
*/ */
export function decodeTokenPayload (token: string): any { export function decodeTokenPayload (token: string): any {
const parts = token.split('.') return JSON.parse(atob(token.split('.')[1]))
}
const payload = parts[1]
export function isAdminUser (): boolean {
const decodedPayload = atob(payload) return decodeTokenPayload(getMetadata(plugin.metadata.Token) ?? '').admin === 'true'
const parsedPayload = JSON.parse(decodedPayload)
return parsedPayload
} }

View File

@ -91,6 +91,7 @@ export class LiveQuery implements WithTx, Client {
private readonly queries: Map<Ref<Class<Doc>>, Query[]> = new Map<Ref<Class<Doc>>, Query[]>() private readonly queries: Map<Ref<Class<Doc>>, Query[]> = new Map<Ref<Class<Doc>>, Query[]>()
private readonly queue: Query[] = [] private readonly queue: Query[] = []
private queryCounter: number = 0 private queryCounter: number = 0
private closed: boolean = false
// A map of _class to documents. // A map of _class to documents.
private readonly documentRefs = new Map<string, Map<Ref<Doc>, DocumentRef>>() private readonly documentRefs = new Map<string, Map<Ref<Doc>, DocumentRef>>()
@ -104,7 +105,12 @@ export class LiveQuery implements WithTx, Client {
await this.refreshConnect() await this.refreshConnect()
} }
public isClosed (): boolean {
return this.closed
}
async close (): Promise<void> { async close (): Promise<void> {
this.closed = true
await this.client.close() await this.client.close()
} }

View File

@ -13,16 +13,16 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { Card } from '@hcengineering/presentation' import { PersonAccount } from '@hcengineering/contact'
import { AccountRole, Doc, getCurrentAccount, Ref } from '@hcengineering/core' import { AccountRole, Doc, Ref, getCurrentAccount } from '@hcengineering/core'
import { Card, isAdminUser } from '@hcengineering/presentation'
import ui, { Button, Label } from '@hcengineering/ui'
import { ObjectPresenter } from '@hcengineering/view-resources'
import view from '@hcengineering/view-resources/src/plugin' import view from '@hcengineering/view-resources/src/plugin'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { PersonAccount } from '@hcengineering/contact'
import { personAccountByIdStore } from '../utils' import { personAccountByIdStore } from '../utils'
import ui, { Button, Label } from '@hcengineering/ui'
import PersonAccountRefPresenter from './PersonAccountRefPresenter.svelte'
import PersonAccountPresenter from './PersonAccountPresenter.svelte' import PersonAccountPresenter from './PersonAccountPresenter.svelte'
import { ObjectPresenter } from '@hcengineering/view-resources' import PersonAccountRefPresenter from './PersonAccountRefPresenter.svelte'
export let object: Doc | Doc[] export let object: Doc | Doc[]
export let deleteAction: () => void | Promise<void> export let deleteAction: () => void | Promise<void>
@ -37,7 +37,8 @@
$: canDelete = $: canDelete =
(skipCheck || (skipCheck ||
(creators.length === 1 && creators.includes(getCurrentAccount()._id as Ref<PersonAccount>)) || (creators.length === 1 && creators.includes(getCurrentAccount()._id as Ref<PersonAccount>)) ||
getCurrentAccount().role === AccountRole.Owner) && getCurrentAccount().role === AccountRole.Owner ||
isAdminUser()) &&
canDeleteExtra canDeleteExtra
$: label = canDelete ? view.string.DeleteObject : view.string.DeletePopupNoPermissionTitle $: label = canDelete ? view.string.DeleteObject : view.string.DeletePopupNoPermissionTitle
</script> </script>

View File

@ -16,20 +16,21 @@
import contact, { Employee } from '@hcengineering/contact' import contact, { Employee } from '@hcengineering/contact'
import { AccountRole, getCurrentAccount, Ref } from '@hcengineering/core' import { AccountRole, getCurrentAccount, Ref } from '@hcengineering/core'
import { tzDateCompare, type Department, type Request, type RequestType, type Staff } from '@hcengineering/hr' import { tzDateCompare, type Department, type Request, type RequestType, type Staff } from '@hcengineering/hr'
import { isAdminUser } from '@hcengineering/presentation'
import { import {
areDatesEqual, areDatesEqual,
day as getDay,
daysInMonth, daysInMonth,
deviceOptionsStore as deviceInfo,
eventToHTMLElement, eventToHTMLElement,
day as getDay,
getWeekDayName, getWeekDayName,
isWeekend, isWeekend,
Label, Label,
LabelAndProps, LabelAndProps,
resizeObserver,
Scroller, Scroller,
showPopup, showPopup,
tooltip, tooltip
deviceOptionsStore as deviceInfo,
resizeObserver
} from '@hcengineering/ui' } from '@hcengineering/ui'
import hr from '../../plugin' import hr from '../../plugin'
import { getHolidayDatesForEmployee, getRequests, isHoliday } from '../../utils' import { getHolidayDatesForEmployee, getRequests, isHoliday } from '../../utils'
@ -183,7 +184,7 @@
} }
function isEditable (employee: Staff): boolean { function isEditable (employee: Staff): boolean {
return editableList.includes(employee._id) && (isFutureDate() || me.role === AccountRole.Owner) return editableList.includes(employee._id) && (isFutureDate() || me.role === AccountRole.Owner || isAdminUser())
} }
function getTooltip (requests: Request[], day: Date, staff: Staff): LabelAndProps | undefined { function getTooltip (requests: Request[], day: Date, staff: Staff): LabelAndProps | undefined {
@ -306,7 +307,7 @@
class="timeline-cell timeline-day-header flex-col-center justify-center" class="timeline-cell timeline-day-header flex-col-center justify-center"
style={getCellStyle()} style={getCellStyle()}
on:click={() => { on:click={() => {
if (isFutureDate() || me.role === AccountRole.Owner) { if (isFutureDate() || me.role === AccountRole.Owner || isAdminUser()) {
setPublicHoliday(day) setPublicHoliday(day)
} }
}} }}

View File

@ -15,7 +15,7 @@
<script lang="ts"> <script lang="ts">
import { PersonAccount } from '@hcengineering/contact' import { PersonAccount } from '@hcengineering/contact'
import { AccountRole, getCurrentAccount, roleOrder } from '@hcengineering/core' import { AccountRole, getCurrentAccount, roleOrder } from '@hcengineering/core'
import presentation, { createQuery, decodeTokenPayload } from '@hcengineering/presentation' import { createQuery, isAdminUser } from '@hcengineering/presentation'
import setting, { SettingsCategory } from '@hcengineering/setting' import setting, { SettingsCategory } from '@hcengineering/setting'
import { import {
Component, Component,
@ -27,7 +27,6 @@
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { onDestroy } from 'svelte' import { onDestroy } from 'svelte'
import { clearSettingsStore } from '../store' import { clearSettingsStore } from '../store'
import { getMetadata } from '@hcengineering/platform'
export let kind: 'navigation' | 'content' | undefined export let kind: 'navigation' | 'content' | undefined
export let categoryName: string export let categoryName: string
@ -39,7 +38,7 @@
let categories: SettingsCategory[] = [] let categories: SettingsCategory[] = []
const account = getCurrentAccount() as PersonAccount const account = getCurrentAccount() as PersonAccount
const admin = decodeTokenPayload(getMetadata(presentation.metadata.Token) ?? '').admin === 'true' const admin = isAdminUser()
const settingsQuery = createQuery() const settingsQuery = createQuery()
settingsQuery.query( settingsQuery.query(
@ -48,7 +47,7 @@
(res) => { (res) => {
categories = roleOrder[account.role] > roleOrder[AccountRole.User] ? res : res.filter((p) => !p.secured) categories = roleOrder[account.role] > roleOrder[AccountRole.User] ? res : res.filter((p) => !p.secured)
if (!admin) { if (!admin) {
categories = categories.filter((p) => !p.adminOnly) categories = categories.filter((p) => !(p.adminOnly ?? false))
} }
category = findCategory(categoryId) category = findCategory(categoryId)
}, },

View File

@ -14,9 +14,17 @@
--> -->
<script lang="ts"> <script lang="ts">
import { Project } from '@hcengineering/tracker' import { Project } from '@hcengineering/tracker'
import { Icon, IconWithEmoji, getPlatformColorDef, getPlatformColorForTextDef, themeStore } from '@hcengineering/ui' import {
Icon,
IconWithEmoji,
getPlatformColorDef,
getPlatformColorForTextDef,
themeStore,
Label
} from '@hcengineering/ui'
import tracker from '../../plugin' import tracker from '../../plugin'
import view from '@hcengineering/view' import view from '@hcengineering/view'
import presentation from '@hcengineering/presentation'
export let value: Project | undefined export let value: Project | undefined
export let inline: boolean = false export let inline: boolean = false
@ -41,6 +49,9 @@
</div> </div>
<span class="label no-underline nowrap" class:fs-bold={accent}> <span class="label no-underline nowrap" class:fs-bold={accent}>
{value.name} {value.name}
{#if value.archived}
<Label label={presentation.string.Archived} />
{/if}
</span> </span>
</div> </div>
{/if} {/if}

View File

@ -17,7 +17,12 @@
import { Analytics } from '@hcengineering/analytics' import { Analytics } from '@hcengineering/analytics'
import core, { import core, {
AccountRole, AccountRole,
type FindOptions, ClassifierKind,
Hierarchy,
TxProcessor,
getCurrentAccount,
getObjectValue,
type Account,
type AggregateValue, type AggregateValue,
type AttachedDoc, type AttachedDoc,
type CategoryType, type CategoryType,
@ -27,9 +32,7 @@ import core, {
type Doc, type Doc,
type DocumentQuery, type DocumentQuery,
type DocumentUpdate, type DocumentUpdate,
getCurrentAccount, type FindOptions,
getObjectValue,
Hierarchy,
type Lookup, type Lookup,
type Mixin, type Mixin,
type Obj, type Obj,
@ -38,38 +41,37 @@ import core, {
type ReverseLookup, type ReverseLookup,
type ReverseLookups, type ReverseLookups,
type Space, type Space,
type TxOperations,
type TxCUD, type TxCUD,
type TxCollectionCUD, type TxCollectionCUD,
TxProcessor,
type TxCreateDoc, type TxCreateDoc,
type TxUpdateDoc,
type TxMixin, type TxMixin,
ClassifierKind, type TxOperations,
type TxUpdateDoc,
type TypeAny type TypeAny
} from '@hcengineering/core' } from '@hcengineering/core'
import { type Restrictions } from '@hcengineering/guest'
import type { Asset, IntlString } from '@hcengineering/platform' import type { Asset, IntlString } from '@hcengineering/platform'
import { getResource, translate } from '@hcengineering/platform' import { getResource, translate } from '@hcengineering/platform'
import { import {
type AttributeCategory,
getAttributePresenterClass, getAttributePresenterClass,
getClient, getClient,
hasResource, hasResource,
isAdminUser,
type AttributeCategory,
type KeyedAttribute type KeyedAttribute
} from '@hcengineering/presentation' } from '@hcengineering/presentation'
import { type Restrictions } from '@hcengineering/guest'
import { import {
type AnyComponent,
type AnySvelteComponent,
ErrorPresenter, ErrorPresenter,
getCurrentResolvedLocation, getCurrentResolvedLocation,
getPanelURI, getPanelURI,
getPlatformColorForText, getPlatformColorForText,
type Location,
locationToUrl, locationToUrl,
navigate, navigate,
resolvedLocationStore, resolvedLocationStore,
themeStore themeStore,
type AnyComponent,
type AnySvelteComponent,
type Location
} from '@hcengineering/ui' } from '@hcengineering/ui'
import view, { import view, {
type AttributeModel, type AttributeModel,
@ -80,10 +82,10 @@ import view, {
type ViewletDescriptor type ViewletDescriptor
} from '@hcengineering/view' } from '@hcengineering/view'
import contact, { getName, type Contact, type PersonAccount } from '@hcengineering/contact'
import { get, writable } from 'svelte/store' import { get, writable } from 'svelte/store'
import plugin from './plugin' import plugin from './plugin'
import { noCategory } from './viewOptions' import { noCategory } from './viewOptions'
import contact, { type Contact, getName } from '@hcengineering/contact'
export { getFiltredKeys, isCollectionAttr } from '@hcengineering/presentation' export { getFiltredKeys, isCollectionAttr } from '@hcengineering/presentation'
@ -386,7 +388,8 @@ export async function buildModel (options: BuildModelOptions): Promise<Attribute
export async function deleteObject (client: TxOperations, object: Doc): Promise<void> { export async function deleteObject (client: TxOperations, object: Doc): Promise<void> {
const currentAcc = getCurrentAccount() const currentAcc = getCurrentAccount()
if (currentAcc.role !== AccountRole.Owner && object.createdBy !== currentAcc._id) return const accounts = await getCurrentPersonAccounts()
if (currentAcc.role !== AccountRole.Owner && !accounts.has(object.createdBy)) return
if (client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) { if (client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) {
const adoc = object as AttachedDoc const adoc = object as AttachedDoc
await client await client
@ -401,13 +404,45 @@ export async function deleteObject (client: TxOperations, object: Doc): Promise<
} }
} }
export async function getCurrentPersonAccounts (): Promise<Set<Ref<Account> | undefined>> {
return new Set(
(
await getClient().findAll(contact.class.PersonAccount, { person: (getCurrentAccount() as PersonAccount).person })
).map((it) => it._id)
)
}
export async function deleteObjects (client: TxOperations, objects: Doc[], skipCheck: boolean = false): Promise<void> { export async function deleteObjects (client: TxOperations, objects: Doc[], skipCheck: boolean = false): Promise<void> {
let realObjects: Doc[] = []
if (!skipCheck) { if (!skipCheck) {
const currentAcc = getCurrentAccount() const currentAcc = getCurrentAccount()
if (currentAcc.role !== AccountRole.Owner && objects.some((p) => p.createdBy !== currentAcc._id)) return
// We need to find all person current accounts
const allPersonAccounts = await getCurrentPersonAccounts()
const byClass = new Map<Ref<Class<Doc>>, Doc[]>()
for (const d of objects) {
byClass.set(d._class, [...(byClass.get(d._class) ?? []), d])
}
const adminUser = isAdminUser()
for (const [cl, docs] of byClass.entries()) {
const realDocs = await client.findAll(cl, { _id: { $in: docs.map((it: Doc) => it._id) } })
const notAllowed = realDocs.filter((p) => !allPersonAccounts.has(p.createdBy))
if (notAllowed.length > 0) {
console.error('You are not allowed to delete this object', notAllowed)
}
if (currentAcc.role === AccountRole.Owner || adminUser) {
realObjects.push(...realDocs)
} else {
realObjects.push(...realDocs.filter((p) => allPersonAccounts.has(p.createdBy)))
}
}
} else {
realObjects = objects
} }
const ops = client.apply('delete') const ops = client.apply('delete')
for (const object of objects) { for (const object of realObjects) {
if (client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) { if (client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) {
const adoc = object as AttachedDoc const adoc = object as AttachedDoc
await ops await ops

View File

@ -521,7 +521,7 @@ export interface Action<T extends Doc = Doc, P = Record<string, any>> extends Do
// For example, it could be global action and action for focus class, second one fill override first one. // For example, it could be global action and action for focus class, second one fill override first one.
override?: Ref<Action>[] override?: Ref<Action>[]
// Avaible only for workspace owners // Available only for workspace owners
secured?: boolean secured?: boolean
allowedForEditableContent?: boolean allowedForEditableContent?: boolean
} }

View File

@ -20,7 +20,7 @@
import notification, { DocNotifyContext, InboxNotification, inboxId } from '@hcengineering/notification' import notification, { DocNotifyContext, InboxNotification, inboxId } from '@hcengineering/notification'
import { BrowserNotificatator, InboxNotificationsClientImpl } from '@hcengineering/notification-resources' import { BrowserNotificatator, InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
import { IntlString, broadcastEvent, getMetadata, getResource } from '@hcengineering/platform' import { IntlString, broadcastEvent, getMetadata, getResource } from '@hcengineering/platform'
import { ActionContext, createQuery, getClient } from '@hcengineering/presentation' import { ActionContext, createQuery, getClient, isAdminUser } from '@hcengineering/presentation'
import setting from '@hcengineering/setting' import setting from '@hcengineering/setting'
import support, { SupportStatus, supportLink } from '@hcengineering/support' import support, { SupportStatus, supportLink } from '@hcengineering/support'
import { import {
@ -633,7 +633,7 @@
}} }}
/> />
</div> </div>
{:else if employee?.active || account.role === AccountRole.Owner} {:else if employee?.active || account.role === AccountRole.Owner || isAdminUser()}
<ActionHandler /> <ActionHandler />
<svg class="svg-mask"> <svg class="svg-mask">
<clipPath id="notify-normal"> <clipPath id="notify-normal">

View File

@ -11,7 +11,7 @@ import core, {
} from '@hcengineering/core' } from '@hcengineering/core'
import login, { loginId } from '@hcengineering/login' import login, { loginId } from '@hcengineering/login'
import { addEventListener, broadcastEvent, getMetadata, getResource, setMetadata } from '@hcengineering/platform' import { addEventListener, broadcastEvent, getMetadata, getResource, setMetadata } from '@hcengineering/platform'
import presentation, { closeClient, refreshClient, setClient } from '@hcengineering/presentation' import presentation, { closeClient, purgeClient, refreshClient, setClient } from '@hcengineering/presentation'
import { import {
fetchMetadataLocalStorage, fetchMetadataLocalStorage,
getCurrentLocation, getCurrentLocation,
@ -77,6 +77,8 @@ export async function connect (title: string): Promise<Client | undefined> {
} }
if (_token !== token && _client !== undefined) { if (_token !== token && _client !== undefined) {
// We need to flush all data from memory
await purgeClient()
await _client.close() await _client.close()
_client = undefined _client = undefined
} }

View File

@ -24,6 +24,7 @@ import WorkbenchApp from './components/WorkbenchApp.svelte'
import { doNavigate } from './utils' import { doNavigate } from './utils'
import Workbench from './components/Workbench.svelte' import Workbench from './components/Workbench.svelte'
import ServerManager from './components/ServerManager.svelte' import ServerManager from './components/ServerManager.svelte'
import { isAdminUser } from '@hcengineering/presentation'
async function hasArchiveSpaces (spaces: Space[]): Promise<boolean> { async function hasArchiveSpaces (spaces: Space[]): Promise<boolean> {
return spaces.find((sp) => sp.archived) !== undefined return spaces.find((sp) => sp.archived) !== undefined
@ -51,7 +52,7 @@ export default async (): Promise<Resources> => ({
}, },
function: { function: {
HasArchiveSpaces: hasArchiveSpaces, HasArchiveSpaces: hasArchiveSpaces,
IsOwner: async (docs: Space[]) => getCurrentAccount().role === AccountRole.Owner IsOwner: async (docs: Space[]) => getCurrentAccount().role === AccountRole.Owner || isAdminUser()
}, },
actionImpl: { actionImpl: {
Navigate: doNavigate Navigate: doNavigate

View File

@ -59,7 +59,7 @@ export class ConfigurationMiddleware extends BaseMiddleware implements Middlewar
if (this.targetDomains.includes(domain)) { if (this.targetDomains.includes(domain)) {
if (ctx.userEmail !== configurationAccountEmail) { if (ctx.userEmail !== configurationAccountEmail) {
const account = await this.getUser(ctx) const account = await this.getUser(ctx)
if (account.role !== AccountRole.Owner) { if (account.role !== AccountRole.Owner && ctx.admin !== true) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {})) throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
} }
} }
@ -82,7 +82,7 @@ export class ConfigurationMiddleware extends BaseMiddleware implements Middlewar
if (this.targetDomains.includes(domain)) { if (this.targetDomains.includes(domain)) {
if (ctx.userEmail !== configurationAccountEmail) { if (ctx.userEmail !== configurationAccountEmail) {
const account = await this.getUser(ctx) const account = await this.getUser(ctx)
if (account.role !== AccountRole.Owner && account._id !== core.account.System) { if (account.role !== AccountRole.Owner && account._id !== core.account.System && ctx.admin !== true) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {})) throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
} }
} }

View File

@ -313,7 +313,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
const space = this.privateSpaces[tx.objectSpace] const space = this.privateSpaces[tx.objectSpace]
if (space !== undefined) { if (space !== undefined) {
targets = await this.getTargets(space.members) targets = await this.getTargets(space.members)
if (!isOwner(account)) { if (!isOwner(account, ctx)) {
const cudTx = tx as TxCUD<Doc> const cudTx = tx as TxCUD<Doc>
const isSpace = h.isDerived(cudTx.objectClass, core.class.Space) const isSpace = h.isDerived(cudTx.objectClass, core.class.Space)
const allowed = this.allowedSpaces[account._id] const allowed = this.allowedSpaces[account._id]
@ -482,7 +482,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
const field = this.getKey(_class) const field = this.getKey(_class)
if (!isSystem(account) && account.role !== AccountRole.Guest) { if (!isSystem(account) && account.role !== AccountRole.Guest) {
if (!isOwner(account) || !this.storage.hierarchy.isDerived(_class, core.class.Space)) { if (!isOwner(account, ctx) || !this.storage.hierarchy.isDerived(_class, core.class.Space)) {
if (query[field] !== undefined) { if (query[field] !== undefined) {
;(newQuery as any)[field] = await this.mergeQuery(account, query[field], domain) ;(newQuery as any)[field] = await this.mergeQuery(account, query[field], domain)
} else { } else {
@ -492,7 +492,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
} }
} }
const findResult = await this.provideFindAll(ctx, _class, newQuery, options) const findResult = await this.provideFindAll(ctx, _class, newQuery, options)
if (!isOwner(account) && account.role !== AccountRole.Guest) { if (!isOwner(account, ctx) && account.role !== AccountRole.Guest) {
if (options?.lookup !== undefined) { if (options?.lookup !== undefined) {
for (const object of findResult) { for (const object of findResult) {
if (object.$lookup !== undefined) { if (object.$lookup !== undefined) {
@ -521,7 +521,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
async isUnavailable (ctx: SessionContext, space: Ref<Space>): Promise<boolean> { async isUnavailable (ctx: SessionContext, space: Ref<Space>): Promise<boolean> {
if (this.privateSpaces[space] === undefined) return false if (this.privateSpaces[space] === undefined) return false
const account = await getUser(this.storage, ctx) const account = await getUser(this.storage, ctx)
if (isOwner(account)) return false if (isOwner(account, ctx)) return false
return !this.allowedSpaces[account._id]?.includes(space) return !this.allowedSpaces[account._id]?.includes(space)
} }

View File

@ -51,8 +51,8 @@ export async function getUser (storage: ServerStorage, ctx: SessionContext): Pro
return account return account
} }
export function isOwner (account: Account): boolean { export function isOwner (account: Account, ctx: SessionContext): boolean {
return account.role === AccountRole.Owner || account._id === core.account.System return account.role === AccountRole.Owner || account._id === core.account.System || ctx.admin === true
} }
export function isSystem (account: Account): boolean { export function isSystem (account: Account): boolean {