diff --git a/plugins/login-resources/src/components/InviteLink.svelte b/plugins/login-resources/src/components/InviteLink.svelte
index d5e5614ea0..8ca7f7998f 100644
--- a/plugins/login-resources/src/components/InviteLink.svelte
+++ b/plugins/login-resources/src/components/InviteLink.svelte
@@ -16,6 +16,7 @@
import { AccountRole, getCurrentAccount, hasAccountRole, Timestamp } from '@hcengineering/core'
import { copyTextToClipboard, createQuery } from '@hcengineering/presentation'
import setting, { RoleCapability } from '@hcengineering/setting'
+ import { getDefaultInviteRole, resolveInviteSettings } from '@hcengineering/setting-resources'
import { getResource } from '@hcengineering/platform'
import { AnySvelteComponent, Button, EditBox, Grid, Label, Loading, MiniToggle, ticker } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
@@ -38,24 +39,18 @@
limit: number | undefined
}
+ const defaultInviteRole: AccountRole = getDefaultInviteRole()
+
$: !ignoreSettings &&
query.query(setting.class.InviteSettings, {}, (set) => {
- if (set !== undefined && set.length > 0) {
- expHours = set[0].expirationTime
- emailMask = set[0].emailMask
- limit = set[0].limit
- if (role == null) {
- role = set[0].defaultInviteRole ?? AccountRole.User
- }
- } else {
- expHours = 48
- limit = -1
- if (role == null) {
- role = AccountRole.User
- }
+ const state = resolveInviteSettings(set?.[0])
+ expHours = state.expirationTime
+ emailMask = state.emailMask
+ limit = state.limit
+ if (role == null) {
+ role = state.defaultInviteRole
}
-
- if (limit === -1) noLimit = true
+ if (state.noLimit) noLimit = true
defaultValues = {
expirationTime: expHours,
@@ -163,7 +158,7 @@
{#if userRoleSelectComponent}
{/if}
@@ -201,7 +196,7 @@
if (!canGenerateInviteLinks) return
const effectiveLimit = limit ?? 0
if (effectiveLimit > 0 || noLimit) {
- void getLink(expHours, emailMask, limit, role ?? AccountRole.User)
+ void getLink(expHours, emailMask, limit, role ?? defaultInviteRole)
}
}}
/>
diff --git a/plugins/setting-resources/package.json b/plugins/setting-resources/package.json
index ec379f482d..5fb70c7335 100644
--- a/plugins/setting-resources/package.json
+++ b/plugins/setting-resources/package.json
@@ -13,7 +13,9 @@
"build:watch": "compile ui",
"_phase:build": "compile ui",
"_phase:format": "format src",
- "_phase:validate": "compile validate"
+ "_phase:validate": "compile validate",
+ "_phase:test": "jest --passWithNoTests --silent",
+ "test": "jest --passWithNoTests --silent"
},
"devDependencies": {
"svelte-loader": "^3.2.0",
diff --git a/plugins/setting-resources/src/__tests__/inviteSettingsUtils.test.ts b/plugins/setting-resources/src/__tests__/inviteSettingsUtils.test.ts
new file mode 100644
index 0000000000..753d46165e
--- /dev/null
+++ b/plugins/setting-resources/src/__tests__/inviteSettingsUtils.test.ts
@@ -0,0 +1,176 @@
+//
+// Copyright © 2026 Hardcore Engineering Inc.
+//
+// Licensed under the Eclipse Public License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License. You may
+// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import { AccountRole } from '@hcengineering/core'
+import { getMetadata } from '@hcengineering/platform'
+import type { InviteSettings } from '@hcengineering/setting'
+import {
+ DEFAULT_INVITE_LINK_GENERATOR_ROLES,
+ getDefaultInviterRoles,
+ getDefaultInviteRole,
+ INVITE_SETTINGS_DEFAULT_EXPIRATION_HOURS,
+ INVITE_SETTINGS_DEFAULT_LIMIT,
+ normalizeInviteRole,
+ normalizeInviteRoles,
+ resolveInviteSettings
+} from '../inviteSettingsUtils'
+
+jest.mock('@hcengineering/platform', () => {
+ const actual = jest.requireActual('@hcengineering/platform')
+ return {
+ ...actual,
+ getMetadata: jest.fn()
+ }
+})
+
+const mockGetMetadata = getMetadata as jest.MockedFunction
+
+function inviteDoc (
+ partial: Partial & Pick
+): InviteSettings {
+ return partial as InviteSettings
+}
+
+describe('inviteSettingsUtils', () => {
+ beforeEach(() => {
+ mockGetMetadata.mockReturnValue(undefined)
+ })
+
+ describe('normalizeInviteRole', () => {
+ it('maps string role names case-insensitively', () => {
+ expect(normalizeInviteRole('GUEST', AccountRole.User)).toBe(AccountRole.Guest)
+ expect(normalizeInviteRole('User', AccountRole.Guest)).toBe(AccountRole.User)
+ expect(normalizeInviteRole('MAINTAINER', AccountRole.Guest)).toBe(AccountRole.Maintainer)
+ expect(normalizeInviteRole('owner', AccountRole.Guest)).toBe(AccountRole.Owner)
+ })
+
+ it('returns valid AccountRole numbers as-is', () => {
+ expect(normalizeInviteRole(AccountRole.Maintainer, AccountRole.Guest)).toBe(AccountRole.Maintainer)
+ })
+
+ it('returns fallback for unknown string', () => {
+ expect(normalizeInviteRole('admin', AccountRole.User)).toBe(AccountRole.User)
+ })
+
+ it('returns fallback for undefined', () => {
+ expect(normalizeInviteRole(undefined, AccountRole.Owner)).toBe(AccountRole.Owner)
+ })
+
+ it('returns fallback for invalid number', () => {
+ expect(normalizeInviteRole(999 as unknown as AccountRole, AccountRole.Guest)).toBe(AccountRole.Guest)
+ })
+ })
+
+ describe('normalizeInviteRoles', () => {
+ it('returns fallback copy when values missing or empty', () => {
+ const fallback = [AccountRole.Guest, AccountRole.User]
+ expect(normalizeInviteRoles(undefined, fallback)).toEqual(fallback)
+ expect(normalizeInviteRoles([], fallback)).toEqual(fallback)
+ expect(normalizeInviteRoles(undefined, fallback)).not.toBe(fallback)
+ })
+
+ it('maps and deduplicates roles', () => {
+ expect(normalizeInviteRoles(['user', 'USER', 'maintainer'], [AccountRole.Guest])).toEqual([
+ AccountRole.User,
+ AccountRole.Maintainer
+ ])
+ })
+
+ it('maps unrecognized strings to User (inner fallback)', () => {
+ expect(normalizeInviteRoles(['nope', 'x'], [AccountRole.Guest])).toEqual([AccountRole.User])
+ })
+ })
+
+ describe('getDefaultInviteRole / getDefaultInviterRoles', () => {
+ it('uses User and default generator list when metadata unset', () => {
+ mockGetMetadata.mockReturnValue(undefined)
+ expect(getDefaultInviteRole()).toBe(AccountRole.User)
+ expect(getDefaultInviterRoles()).toEqual(DEFAULT_INVITE_LINK_GENERATOR_ROLES)
+ })
+
+ it('reads default invite role from metadata string', () => {
+ mockGetMetadata.mockReturnValue('maintainer')
+ expect(getDefaultInviteRole()).toBe(AccountRole.Maintainer)
+ })
+ })
+
+ describe('resolveInviteSettings', () => {
+ it('returns defaults when doc is undefined', () => {
+ mockGetMetadata.mockReturnValue(undefined)
+ const r = resolveInviteSettings(undefined)
+ expect(r).toEqual({
+ expirationTime: INVITE_SETTINGS_DEFAULT_EXPIRATION_HOURS,
+ emailMask: '',
+ limit: INVITE_SETTINGS_DEFAULT_LIMIT,
+ defaultInviteRole: AccountRole.User,
+ inviteLinkGeneratorRoles: DEFAULT_INVITE_LINK_GENERATOR_ROLES,
+ noLimit: true
+ })
+ })
+
+ it('uses doc fields and noLimit when limit is not -1', () => {
+ const doc = inviteDoc({
+ expirationTime: 12,
+ emailMask: '*@corp.test',
+ limit: 100,
+ defaultInviteRole: AccountRole.Guest,
+ inviteLinkGeneratorRoles: [AccountRole.Owner]
+ })
+ const r = resolveInviteSettings(doc)
+ expect(r.expirationTime).toBe(12)
+ expect(r.emailMask).toBe('*@corp.test')
+ expect(r.limit).toBe(100)
+ expect(r.defaultInviteRole).toBe(AccountRole.Guest)
+ expect(r.inviteLinkGeneratorRoles).toEqual([AccountRole.Owner])
+ expect(r.noLimit).toBe(false)
+ })
+
+ it('sets noLimit true when doc.limit is -1', () => {
+ const doc = inviteDoc({
+ expirationTime: 48,
+ emailMask: '',
+ limit: -1,
+ defaultInviteRole: AccountRole.User,
+ inviteLinkGeneratorRoles: [AccountRole.User]
+ })
+ expect(resolveInviteSettings(doc).noLimit).toBe(true)
+ })
+
+ it('uses DEFAULT_INVITE_LINK_GENERATOR_ROLES copy when doc list empty', () => {
+ const doc = inviteDoc({
+ expirationTime: 48,
+ emailMask: '',
+ limit: -1,
+ defaultInviteRole: AccountRole.User,
+ inviteLinkGeneratorRoles: []
+ })
+ const r = resolveInviteSettings(doc)
+ expect(r.inviteLinkGeneratorRoles).toEqual(DEFAULT_INVITE_LINK_GENERATOR_ROLES)
+ expect(r.inviteLinkGeneratorRoles).not.toBe(DEFAULT_INVITE_LINK_GENERATOR_ROLES)
+ })
+
+ it('normalizes string defaultInviteRole using metadata fallback', () => {
+ mockGetMetadata.mockReturnValue('user')
+ const doc = inviteDoc({
+ expirationTime: 1,
+ emailMask: '',
+ limit: -1,
+ defaultInviteRole: 'guest' as unknown as AccountRole,
+ inviteLinkGeneratorRoles: [AccountRole.User]
+ })
+ expect(resolveInviteSettings(doc).defaultInviteRole).toBe(AccountRole.Guest)
+ })
+ })
+})
diff --git a/plugins/setting-resources/src/__tests__/roleCapability.test.ts b/plugins/setting-resources/src/__tests__/roleCapability.test.ts
new file mode 100644
index 0000000000..48ed06d093
--- /dev/null
+++ b/plugins/setting-resources/src/__tests__/roleCapability.test.ts
@@ -0,0 +1,67 @@
+//
+// Copyright © 2026 Hardcore Engineering Inc.
+//
+// Licensed under the Eclipse Public License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License. You may
+// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import { AccountRole, type Account } from '@hcengineering/core'
+import { RoleCapability } from '@hcengineering/setting'
+import { DEFAULT_INVITE_LINK_GENERATOR_ROLES } from '../inviteSettingsUtils'
+import { getRolesForCapability, hasRoleCapability } from '../roleCapability'
+
+function account (role: AccountRole): Account {
+ return { role } as any
+}
+
+describe('roleCapability (Generate invite link permission)', () => {
+ describe('getRolesForCapability', () => {
+ it('uses RoleCapabilitySettings when set', () => {
+ expect(
+ getRolesForCapability(RoleCapability.GenerateInviteLink, {
+ [RoleCapability.GenerateInviteLink]: [AccountRole.Owner]
+ })
+ ).toEqual([AccountRole.Owner])
+ })
+
+ it('uses inviteLinkGeneratorRoles when capability map missing', () => {
+ expect(
+ getRolesForCapability(RoleCapability.GenerateInviteLink, undefined, [AccountRole.Guest, AccountRole.User])
+ ).toEqual([AccountRole.Guest, AccountRole.User])
+ })
+
+ it('falls back to DEFAULT_INVITE_LINK_GENERATOR_ROLES (shared with invite settings)', () => {
+ expect(getRolesForCapability(RoleCapability.GenerateInviteLink, undefined, undefined)).toBe(
+ DEFAULT_INVITE_LINK_GENERATOR_ROLES
+ )
+ })
+ })
+
+ describe('hasRoleCapability', () => {
+ it('allows User when falling back to default generator roles', () => {
+ expect(
+ hasRoleCapability(account(AccountRole.User), RoleCapability.GenerateInviteLink, undefined, undefined)
+ ).toBe(true)
+ })
+
+ it('denies User when only Owner may generate', () => {
+ expect(
+ hasRoleCapability(account(AccountRole.User), RoleCapability.GenerateInviteLink, undefined, [AccountRole.Owner])
+ ).toBe(false)
+ })
+
+ it('allows Owner when only Owner may generate', () => {
+ expect(
+ hasRoleCapability(account(AccountRole.Owner), RoleCapability.GenerateInviteLink, undefined, [AccountRole.Owner])
+ ).toBe(true)
+ })
+ })
+})
diff --git a/plugins/setting-resources/src/components/InviteSetting.svelte b/plugins/setting-resources/src/components/InviteSetting.svelte
index 97d8793b28..73982e95e6 100644
--- a/plugins/setting-resources/src/components/InviteSetting.svelte
+++ b/plugins/setting-resources/src/components/InviteSetting.svelte
@@ -18,7 +18,8 @@
import { createQuery, getClient } from '@hcengineering/presentation'
import setting, { type InviteSettings, type RoleCapabilitySettings, RoleCapability } from '@hcengineering/setting'
import { hasRoleCapability } from '../roleCapability'
- import { getMetadata, translate } from '@hcengineering/platform'
+ import { getDefaultInviterRoles, getDefaultInviteRole, resolveInviteSettings } from '../inviteSettingsUtils'
+ import { translate } from '@hcengineering/platform'
import {
Breadcrumb,
DropdownLabels,
@@ -36,41 +37,13 @@
const client = getClient()
- const configDefaultInviteRole = getMetadata(setting.metadata.DefaultInviteRole)
- const configInviteLinkGeneratorRoles = getMetadata(setting.metadata.DefaultInviteLinkGeneratorRoles)
-
- function normalizeRole (value: string | undefined, fallback: AccountRole): AccountRole {
- if (typeof value === 'string') {
- const normalizedValue = value.toLowerCase()
- switch (normalizedValue) {
- case 'guest':
- return AccountRole.Guest
- case 'user':
- return AccountRole.User
- case 'maintainer':
- return AccountRole.Maintainer
- case 'owner':
- return AccountRole.Owner
- }
- }
- return fallback
- }
-
- function normalizeRoles (values: Array | undefined, fallback: AccountRole[]): AccountRole[] {
- if (!Array.isArray(values) || values.length === 0) return [...fallback]
- const mapped = values
- .map((v) => normalizeRole(v, AccountRole.User))
- .filter((role, index, arr) => arr.indexOf(role) === index)
- return mapped.length > 0 ? mapped : [...fallback]
- }
let loading = true
let expTime: number = 48
let mask: string = ''
let limit: number | undefined = -1
- const defaultGeneratorRoles: AccountRole[] = [AccountRole.User, AccountRole.Maintainer, AccountRole.Owner]
- let defaultInviteRole: AccountRole = normalizeRole(configDefaultInviteRole, AccountRole.User)
- let inviteLinkGeneratorRoles: AccountRole[] = normalizeRoles(configInviteLinkGeneratorRoles, defaultGeneratorRoles)
+ let defaultInviteRole: AccountRole = getDefaultInviteRole()
+ let inviteLinkGeneratorRoles: AccountRole[] = getDefaultInviterRoles()
let noLimit: boolean = true
let existingInviteSettings: InviteSettings[] = []
let existingRoleCapabilitySettings: {
@@ -108,24 +81,13 @@
function applyInviteSettings (set: InviteSettings[]): void {
existingInviteSettings = set
- if (existingInviteSettings.length > 0) {
- const first = existingInviteSettings[0]
- expTime = first.expirationTime
- mask = first.emailMask
- limit = first.limit
- defaultInviteRole = normalizeRole(first.defaultInviteRole, defaultInviteRole)
- inviteLinkGeneratorRoles =
- first.inviteLinkGeneratorRoles != null && first.inviteLinkGeneratorRoles.length > 0
- ? normalizeRoles(first.inviteLinkGeneratorRoles, defaultGeneratorRoles)
- : [...defaultGeneratorRoles]
- } else {
- expTime = 48
- mask = ''
- limit = -1
- defaultInviteRole = normalizeRole(configDefaultInviteRole, AccountRole.User)
- inviteLinkGeneratorRoles = normalizeRoles(configInviteLinkGeneratorRoles, defaultGeneratorRoles)
- }
- noLimit = limit === -1
+ const state = resolveInviteSettings(set[0])
+ expTime = state.expirationTime
+ mask = state.emailMask
+ limit = state.limit
+ defaultInviteRole = state.defaultInviteRole
+ inviteLinkGeneratorRoles = state.inviteLinkGeneratorRoles
+ noLimit = state.noLimit
loading = false
}
diff --git a/plugins/setting-resources/src/hasRoleCapabilityAsync.ts b/plugins/setting-resources/src/hasRoleCapabilityAsync.ts
index 7835649093..3b0144d413 100644
--- a/plugins/setting-resources/src/hasRoleCapabilityAsync.ts
+++ b/plugins/setting-resources/src/hasRoleCapabilityAsync.ts
@@ -17,6 +17,7 @@ import { getCurrentAccount } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import setting, { RoleCapability } from '@hcengineering/setting'
import { hasRoleCapability } from './roleCapability'
+import { resolveInviteSettings } from './inviteSettingsUtils'
/**
* Returns whether the current account has the given role capability.
@@ -32,10 +33,8 @@ export async function hasRoleCapabilityAsync (capabilityId: RoleCapabilityId | s
const firstInvite = inviteSettings[0] as InviteSettings | undefined
const firstRoleCap = roleCapabilitySettings[0] as RoleCapabilitySettings | undefined
const inviteLinkGeneratorRoles =
- capabilityId === RoleCapability.GenerateInviteLink &&
- firstInvite?.inviteLinkGeneratorRoles != null &&
- firstInvite.inviteLinkGeneratorRoles.length > 0
- ? firstInvite.inviteLinkGeneratorRoles
+ capabilityId === RoleCapability.GenerateInviteLink
+ ? resolveInviteSettings(firstInvite).inviteLinkGeneratorRoles
: undefined
const roleByCapability = firstRoleCap?.roleByCapability
const account = getCurrentAccount()
diff --git a/plugins/setting-resources/src/index.ts b/plugins/setting-resources/src/index.ts
index 5248ce157a..30a09f6a3f 100644
--- a/plugins/setting-resources/src/index.ts
+++ b/plugins/setting-resources/src/index.ts
@@ -82,6 +82,7 @@ import { hasRoleCapabilityAsync } from './hasRoleCapabilityAsync'
export * from './store'
export { hasRoleCapability, getRolesForCapability } from './roleCapability'
export { hasRoleCapabilityAsync } from './hasRoleCapabilityAsync'
+export * from './inviteSettingsUtils'
export {
ClassAttributes,
ClassAttributesList,
diff --git a/plugins/setting-resources/src/inviteSettingsUtils.ts b/plugins/setting-resources/src/inviteSettingsUtils.ts
new file mode 100644
index 0000000000..d6d29454c4
--- /dev/null
+++ b/plugins/setting-resources/src/inviteSettingsUtils.ts
@@ -0,0 +1,114 @@
+//
+// Copyright © 2026 Hardcore Engineering Inc.
+//
+// Licensed under the Eclipse Public License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License. You may
+// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import { AccountRole } from '@hcengineering/core'
+import { getMetadata, type Metadata } from '@hcengineering/platform'
+import setting, { type InviteSettings } from '@hcengineering/setting'
+
+const settingInviteMetadata = setting.metadata as unknown as {
+ DefaultInviteRole: Metadata
+ DefaultInviteLinkGeneratorRoles: Metadata
+}
+
+const numericAccountRoles = new Set(Object.values(AccountRole).filter((v) => typeof v === 'number') as number[])
+
+export function normalizeInviteRole (value: string | AccountRole | undefined, fallback: AccountRole): AccountRole {
+ if (value !== undefined && typeof value === 'number' && numericAccountRoles.has(value)) {
+ return value as AccountRole
+ }
+ if (typeof value === 'string') {
+ const n = value.toLowerCase()
+ switch (n) {
+ case 'guest':
+ return AccountRole.Guest
+ case 'user':
+ return AccountRole.User
+ case 'maintainer':
+ return AccountRole.Maintainer
+ case 'owner':
+ return AccountRole.Owner
+ }
+ }
+ return fallback
+}
+
+export function normalizeInviteRoles (
+ values: Array | undefined,
+ fallback: AccountRole[]
+): AccountRole[] {
+ if (!Array.isArray(values) || values.length === 0) return [...fallback]
+ const mapped = values.map((v) => normalizeInviteRole(v, AccountRole.User)).filter((r, i, arr) => arr.indexOf(r) === i)
+ return mapped.length > 0 ? mapped : [...fallback]
+}
+
+export const DEFAULT_INVITE_LINK_GENERATOR_ROLES: AccountRole[] = [
+ AccountRole.User,
+ AccountRole.Maintainer,
+ AccountRole.Owner
+]
+
+export const INVITE_SETTINGS_DEFAULT_EXPIRATION_HOURS = 48
+export const INVITE_SETTINGS_DEFAULT_LIMIT = -1
+
+export function getDefaultInviteRole (): AccountRole {
+ return normalizeInviteRole(getMetadata(settingInviteMetadata.DefaultInviteRole), AccountRole.User)
+}
+
+export function getDefaultInviterRoles (): AccountRole[] {
+ return normalizeInviteRoles(
+ getMetadata(settingInviteMetadata.DefaultInviteLinkGeneratorRoles),
+ DEFAULT_INVITE_LINK_GENERATOR_ROLES
+ )
+}
+
+export interface ResolvedInviteSettings {
+ expirationTime: number
+ emailMask: string
+ limit: number
+ defaultInviteRole: AccountRole
+ inviteLinkGeneratorRoles: AccountRole[]
+ noLimit: boolean
+}
+
+export function resolveInviteSettings (doc: InviteSettings | undefined): ResolvedInviteSettings {
+ const metaRole = getDefaultInviteRole()
+ const metaGenerators = getDefaultInviterRoles()
+
+ if (doc != null) {
+ return {
+ expirationTime: doc.expirationTime,
+ emailMask: doc.emailMask,
+ limit: doc.limit,
+ defaultInviteRole: normalizeInviteRole(doc.defaultInviteRole as string | AccountRole | undefined, metaRole),
+ inviteLinkGeneratorRoles:
+ doc.inviteLinkGeneratorRoles != null && doc.inviteLinkGeneratorRoles.length > 0
+ ? normalizeInviteRoles(
+ doc.inviteLinkGeneratorRoles as Array,
+ DEFAULT_INVITE_LINK_GENERATOR_ROLES
+ )
+ : [...DEFAULT_INVITE_LINK_GENERATOR_ROLES],
+ noLimit: doc.limit === -1
+ }
+ }
+
+ return {
+ expirationTime: INVITE_SETTINGS_DEFAULT_EXPIRATION_HOURS,
+ emailMask: '',
+ limit: INVITE_SETTINGS_DEFAULT_LIMIT,
+ defaultInviteRole: metaRole,
+ inviteLinkGeneratorRoles: metaGenerators,
+ noLimit: true
+ }
+}
diff --git a/plugins/setting-resources/src/roleCapability.ts b/plugins/setting-resources/src/roleCapability.ts
index 0157f21640..e63efdf338 100644
--- a/plugins/setting-resources/src/roleCapability.ts
+++ b/plugins/setting-resources/src/roleCapability.ts
@@ -16,15 +16,17 @@ import type { Account } from '@hcengineering/core'
import { AccountRole, hasAccountRole } from '@hcengineering/core'
import { RoleCapability, type RoleCapabilityId } from '@hcengineering/setting'
+import { DEFAULT_INVITE_LINK_GENERATOR_ROLES } from './inviteSettingsUtils'
+
/** Default roles that have each capability when no RoleCapabilitySettings is set */
const DEFAULT_ROLES_BY_CAPABILITY: Record = {
- [RoleCapability.GenerateInviteLink]: [AccountRole.User, AccountRole.Maintainer, AccountRole.Owner],
+ [RoleCapability.GenerateInviteLink]: DEFAULT_INVITE_LINK_GENERATOR_ROLES,
[RoleCapability.ManageInviteSettings]: [AccountRole.Maintainer, AccountRole.Owner]
}
/**
* Returns whether the given account has the specified capability.
- * Uses roleByCapability if provided; otherwise for GenerateInviteLink falls back to inviteLinkGeneratorRoles; otherwise uses built-in defaults.
+ * Uses roleByCapability if provided; otherwise for GenerateInviteLink falls back to inviteLinkGeneratorRoles (from workspace settings / resolved defaults); otherwise {@link DEFAULT_INVITE_LINK_GENERATOR_ROLES}.
* @public
*/
export function hasRoleCapability (