mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-30 13:35:04 +02:00
Add a separate setting to control if guests can join WS (#9540)
Signed-off-by: Anton Alexeyev <alexeyev.anton@gmail.com>
This commit is contained in:
@@ -117,6 +117,7 @@ export interface AccountClient {
|
||||
updateAllowReadOnlyGuests: (
|
||||
readOnlyGuestsAllowed: boolean
|
||||
) => Promise<{ guestPerson: Person, guestSocialIds: SocialId[] } | undefined>
|
||||
updateAllowGuestSignUp: (guestSignUpAllowed: boolean) => Promise<void>
|
||||
updateWorkspaceName: (name: string) => Promise<void>
|
||||
deleteWorkspace: () => Promise<void>
|
||||
findPersonBySocialKey: (socialKey: string, requireAccount?: boolean) => Promise<PersonUuid | undefined>
|
||||
@@ -659,6 +660,15 @@ class AccountClientImpl implements AccountClient {
|
||||
return await this.rpc(request)
|
||||
}
|
||||
|
||||
async updateAllowGuestSignUp (guestSignUpAllowed: boolean): Promise<void> {
|
||||
const request = {
|
||||
method: 'updateAllowGuestSignUp' as const,
|
||||
params: { guestSignUpAllowed }
|
||||
}
|
||||
|
||||
await this.rpc(request)
|
||||
}
|
||||
|
||||
async getWorkspaceMembers (): Promise<WorkspaceMemberInfo[]> {
|
||||
const request = {
|
||||
method: 'getWorkspaceMembers' as const,
|
||||
|
||||
@@ -57,6 +57,7 @@ export interface WorkspaceLoginInfo extends LoginInfo {
|
||||
endpoint: string
|
||||
token: string
|
||||
role: AccountRole
|
||||
allowGuestSignUp?: boolean
|
||||
}
|
||||
|
||||
export interface WorkspaceInviteInfo {
|
||||
@@ -122,5 +123,5 @@ export interface AccountAggregatedInfo extends AccountInfo, Person {
|
||||
uuid: AccountUuid
|
||||
integrations: Omit<Integration, 'data'>[]
|
||||
socialIds: SocialId[]
|
||||
workspaces: Omit<WorkspaceInfo, 'allowReadOnlyGuest'>[]
|
||||
workspaces: Omit<WorkspaceInfo, 'allowReadOnlyGuest' | 'allowGuestSignUp'>[]
|
||||
}
|
||||
|
||||
@@ -832,6 +832,7 @@ export interface WorkspaceInfo {
|
||||
createdBy?: PersonUuid // Should always be set for NEW workspaces
|
||||
billingAccount?: PersonUuid // Should always be set for NEW workspaces
|
||||
allowReadOnlyGuest?: boolean // Should always be set for NEW workspaces
|
||||
allowGuestSignUp?: boolean // Should always be set for NEW workspaces
|
||||
}
|
||||
|
||||
export interface BackupStatus {
|
||||
|
||||
@@ -169,6 +169,7 @@
|
||||
"Customize": "Přizpůsobit",
|
||||
"GuestAccess": "Anonymní hosté",
|
||||
"GuestAccessDescription": "Umožňuje anonymním uživatelům prohlížet pracovní prostor v režimu pouze pro čtení",
|
||||
"GuestSignUpDescription": "Umožňuje anonymním uživatelům připojit se k vašemu pracovnímu prostoru jako hosté s omezenými editačními právy",
|
||||
"ManageIdentities": "Spravovat identity",
|
||||
"Release": "Uvolnění",
|
||||
"ReleaseSocialId": "Uvolnit sociální ID",
|
||||
|
||||
@@ -169,6 +169,7 @@
|
||||
"Customize": "Anpassen",
|
||||
"GuestAccess": "Anonyme Gäste",
|
||||
"GuestAccessDescription": "Ermöglicht anonymen Benutzern den schreibgeschützten Zugriff auf den Arbeitsbereich",
|
||||
"GuestSignUpDescription": "Ermöglicht anonymen Benutzern den Beitritt zu Ihrem Arbeitsbereich als Gäste mit eingeschränkten Bearbeitungsrechten",
|
||||
"ManageIdentities": "Identitäten verwalten",
|
||||
"Release": "Freigeben",
|
||||
"ReleaseSocialId": "Social ID freigeben",
|
||||
|
||||
@@ -169,6 +169,7 @@
|
||||
"Customize": "Customize",
|
||||
"GuestAccess": "Anonymous guests",
|
||||
"GuestAccessDescription": "Allows anonymous users to visit your workspace in read-only mode",
|
||||
"GuestSignUpDescription": "Allows anonymous users to join your workspace as guests with limited editing rights",
|
||||
"ManageIdentities": "Manage identities",
|
||||
"Release": "Release",
|
||||
"ReleaseSocialId": "Release social ID",
|
||||
|
||||
@@ -160,6 +160,7 @@
|
||||
"Customize": "Personalizar",
|
||||
"GuestAccess": "Invitados anónimos",
|
||||
"GuestAccessDescription": "Permite a usuarios anónimos visitar el espacio de trabajo en modo de solo lectura",
|
||||
"GuestSignUpDescription": "Permite a usuarios anónimos unirse a su espacio de trabajo como invitados con derechos de edición limitados",
|
||||
"ManageIdentities": "Gestionar identidades",
|
||||
"Release": "Liberar",
|
||||
"ReleaseSocialId": "Liberar ID social",
|
||||
|
||||
@@ -169,6 +169,7 @@
|
||||
"Customize": "Personnaliser",
|
||||
"GuestAccess": "Invités anonymes",
|
||||
"GuestAccessDescription": "Autorise les utilisateurs anonymes à accéder à l'espace de travail en lecture seule",
|
||||
"GuestSignUpDescription": "Permet aux utilisateurs anonymes de rejoindre votre espace de travail en tant qu'invités avec des droits d'édition limités",
|
||||
"ManageIdentities": "Gérer les identités",
|
||||
"Release": "Libération",
|
||||
"ReleaseSocialId": "Libérer l'ID social",
|
||||
|
||||
@@ -169,6 +169,7 @@
|
||||
"Customize": "Personalizzare",
|
||||
"GuestAccess": "Ospiti anonimi",
|
||||
"GuestAccessDescription": "Consente agli utenti anonimi di accedere all'area di lavoro in modalità sola lettura",
|
||||
"GuestSignUpDescription": "Consente agli utenti anonimi di unirsi al tuo spazio di lavoro come ospiti con diritti di modifica limitati",
|
||||
"ManageIdentities": "Gestisci identità",
|
||||
"Release": "Rilascio",
|
||||
"ReleaseSocialId": "Rilascia ID social",
|
||||
|
||||
@@ -169,6 +169,7 @@
|
||||
"Customize": "カスタマイズ",
|
||||
"GuestAccess": "匿名ゲスト",
|
||||
"GuestAccessDescription": "匿名ユーザーがワークスペースを読み取り専用モードで閲覧できます",
|
||||
"GuestSignUpDescription": "匿名ユーザーがゲストとしてワークスペースに参加し、制限された編集権限を得られるようにします",
|
||||
"ManageIdentities": "IDを管理",
|
||||
"Release": "リリース",
|
||||
"ReleaseSocialId": "ソーシャルIDをリリース",
|
||||
|
||||
@@ -160,6 +160,7 @@
|
||||
"Customize": "Personalizar",
|
||||
"GuestAccess": "Convidados anônimos",
|
||||
"GuestAccessDescription": "Permite que usuários anônimos acessem seu espaço de trabalho em modo somente leitura",
|
||||
"GuestSignUpDescription": "Permite que usuários anônimos entrem em seu espaço de trabalho como convidados com direitos de edição limitados",
|
||||
"ManageIdentities": "Gerenciar identidades",
|
||||
"Release": "Libertação",
|
||||
"ReleaseSocialId": "Liberar ID social",
|
||||
|
||||
@@ -169,6 +169,7 @@
|
||||
"Customize": "Настроить",
|
||||
"GuestAccess": "Анонимные гости",
|
||||
"GuestAccessDescription": "Позволяет анонимным пользователям просматривать рабочее пространство в режиме только для чтения",
|
||||
"GuestSignUpDescription": "Позволяет анонимным пользователям присоединяться к вашему рабочему пространству в качестве гостей с ограниченными правами редактирования",
|
||||
"ManageIdentities": "Управление идентификаторами",
|
||||
"Release": "Освободить",
|
||||
"ReleaseSocialId": "Освободить социальный ID",
|
||||
|
||||
@@ -169,6 +169,7 @@
|
||||
"Customize": "自定义",
|
||||
"GuestAccess": "匿名访客",
|
||||
"GuestAccessDescription": "允许匿名用户以只读模式访问工作区",
|
||||
"GuestSignUpDescription": "允许匿名用户以访客身份加入您的工作区,获得有限的编辑权限",
|
||||
"ManageIdentities": "管理身份",
|
||||
"Release": "释放",
|
||||
"ReleaseSocialId": "释放社交ID",
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
let oldName: string
|
||||
let name: string = ''
|
||||
let allowReadOnlyGuests: boolean
|
||||
let allowGuestSignUp: boolean
|
||||
|
||||
const accountClient = getAccountClient()
|
||||
const disabledSet = ['\n', '<', '>', '/', '\\']
|
||||
@@ -77,6 +78,7 @@
|
||||
oldName = res.name
|
||||
name = oldName
|
||||
allowReadOnlyGuests = res.allowReadOnlyGuest ?? false
|
||||
allowGuestSignUp = res.allowGuestSignUp ?? false
|
||||
loading = false
|
||||
}
|
||||
|
||||
@@ -155,6 +157,7 @@
|
||||
async function handleToggleReadonlyAccess (e: CustomEvent<boolean>): Promise<void> {
|
||||
const enabled = e.detail
|
||||
const guestUserInfo = await accountClient.updateAllowReadOnlyGuests(enabled)
|
||||
allowReadOnlyGuests = enabled
|
||||
if (guestUserInfo !== undefined) {
|
||||
const guestAccount: Account = {
|
||||
uuid: guestUserInfo.guestPerson.uuid as AccountUuid,
|
||||
@@ -181,6 +184,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function handleToggleGuestSignUp (e: CustomEvent<boolean>): Promise<void> {
|
||||
await accountClient.updateAllowGuestSignUp(e.detail)
|
||||
}
|
||||
|
||||
function handleTogglePermissions (): void {
|
||||
const newState = !arePermissionsDisabled
|
||||
showPopup(MessageBox, {
|
||||
@@ -223,6 +230,7 @@
|
||||
selected = items[savedFirstDayOfWeek === 'system' ? 0 : $deviceInfo.firstDayOfWeek + 1].id
|
||||
}
|
||||
)
|
||||
|
||||
const onSelected = (e: CustomEvent<string>): void => {
|
||||
selected = e.detail
|
||||
localStorage.setItem('firstDayOfWeek', `${e.detail}`)
|
||||
@@ -302,6 +310,16 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-row-center flex-gap-4">
|
||||
<Label label={settingsRes.string.GuestSignUpDescription} />
|
||||
<Toggle
|
||||
disabled={!allowReadOnlyGuests}
|
||||
on={allowGuestSignUp}
|
||||
on:change={(e) => {
|
||||
void handleToggleGuestSignUp(e)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="delete">
|
||||
<Button
|
||||
kind="regular"
|
||||
|
||||
@@ -128,6 +128,7 @@ export default mergeIds(settingId, setting, {
|
||||
BetaWarning: '' as IntlString,
|
||||
GuestAccess: '' as IntlString,
|
||||
GuestAccessDescription: '' as IntlString,
|
||||
GuestSignUpDescription: '' as IntlString,
|
||||
ManageIdentities: '' as IntlString,
|
||||
AddNew: '' as IntlString,
|
||||
Release: '' as IntlString,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Button, navigate, Notification, NotificationToast } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { getCurrentWorkspaceUrl } from '@hcengineering/presentation'
|
||||
import { allowGuestSignUpStore } from '../utils'
|
||||
|
||||
export let onRemove: () => void
|
||||
export let notification: Notification
|
||||
@@ -16,7 +17,11 @@
|
||||
{notification.subTitle}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="buttons">
|
||||
<Button label={view.string.ReadOnlyJoinWorkspace} stopPropagation={false} on:click={joinWorkspace} />
|
||||
{#if $allowGuestSignUpStore}
|
||||
<Button label={view.string.ReadOnlyJoinWorkspace} stopPropagation={false} on:click={joinWorkspace} />
|
||||
{:else}
|
||||
<div style="width: auto" />
|
||||
{/if}
|
||||
<a href="https://huly.io/signup" target="_blank">
|
||||
<Button label={view.string.ReadOnlySignUp} stopPropagation={false} />
|
||||
</a>
|
||||
|
||||
@@ -1526,6 +1526,7 @@ function getAttrEditor (key: KeyedAttribute, hierarchy: Hierarchy): AnyComponent
|
||||
}
|
||||
}
|
||||
|
||||
export const allowGuestSignUpStore = writable<boolean>(false)
|
||||
export const accessDeniedStore = writable<boolean>(false)
|
||||
|
||||
const spaceSpaceQuery = createQuery(true)
|
||||
|
||||
@@ -55,6 +55,7 @@ import { get, writable } from 'svelte/store'
|
||||
import plugin from './plugin'
|
||||
import { logOut, workspaceCreating } from './utils'
|
||||
import { WorkbenchEvents } from '@hcengineering/workbench'
|
||||
import { allowGuestSignUpStore } from '@hcengineering/view-resources'
|
||||
|
||||
export const versionError = writable<string | undefined>(undefined)
|
||||
const versionStorageKey = 'last_server_version'
|
||||
@@ -405,6 +406,8 @@ export async function connect (title: string): Promise<Client | undefined> {
|
||||
console.log('Logged in with account: ', me)
|
||||
setCurrentAccount(me)
|
||||
|
||||
allowGuestSignUpStore.set(workspaceLoginInfo.allowGuestSignUp ?? false)
|
||||
|
||||
if (me.role === AccountRole.ReadOnlyGuest) {
|
||||
await broadcastEvent(PlatformEvent, new Status(Severity.INFO, platform.status.ReadOnlyAccount, {}))
|
||||
} else {
|
||||
|
||||
@@ -1147,7 +1147,8 @@ describe('MongoAccountDB', () => {
|
||||
const workspaceData = {
|
||||
name: 'New Workspace',
|
||||
url: 'new-workspace',
|
||||
allowReadOnlyGuest: false
|
||||
allowReadOnlyGuest: false,
|
||||
allowGuestSignUp: false
|
||||
}
|
||||
const statusData = {
|
||||
mode: 'active' as const,
|
||||
|
||||
@@ -1941,7 +1941,8 @@ describe('account utils', () => {
|
||||
uuid: 'test-workspace-uuid' as WorkspaceUuid,
|
||||
name: 'Test Workspace',
|
||||
url: 'test-workspace',
|
||||
allowReadOnlyGuest: false
|
||||
allowReadOnlyGuest: false,
|
||||
allowGuestSignUp: false
|
||||
}
|
||||
|
||||
test('should generate invite email content', async () => {
|
||||
|
||||
@@ -598,6 +598,15 @@ export class MongoAccountDB implements AccountDB {
|
||||
)
|
||||
}
|
||||
|
||||
async updateAllowGuestSignUp (workspaceId: WorkspaceUuid, guestSignUpAllowed: boolean): Promise<void> {
|
||||
await this.workspace.update(
|
||||
{
|
||||
uuid: workspaceId
|
||||
},
|
||||
{ allowGuestSignUp: guestSignUpAllowed }
|
||||
)
|
||||
}
|
||||
|
||||
async getPendingWorkspace (
|
||||
region: string,
|
||||
version: Data<Version>,
|
||||
|
||||
@@ -31,7 +31,8 @@ export function getMigrations (ns: string): [string, string][] {
|
||||
getV10Migration2(ns),
|
||||
getV11Migration(ns),
|
||||
getV12Migration(ns),
|
||||
getV13Migration(ns)
|
||||
getV13Migration(ns),
|
||||
getV14Migration(ns)
|
||||
]
|
||||
}
|
||||
|
||||
@@ -414,3 +415,13 @@ function getV13Migration (ns: string): [string, string] {
|
||||
`
|
||||
]
|
||||
}
|
||||
|
||||
function getV14Migration (ns: string): [string, string] {
|
||||
return [
|
||||
'account_db_v14_add_allow_guest_signup_flag_to_workspace',
|
||||
`
|
||||
ALTER TABLE ${ns}.workspace
|
||||
ADD COLUMN IF NOT EXISTS allow_guest_sign_up BOOL NOT NULL DEFAULT FALSE;
|
||||
`
|
||||
]
|
||||
}
|
||||
|
||||
@@ -769,6 +769,11 @@ export class PostgresAccountDB implements AccountDB {
|
||||
.client`UPDATE ${this.client(this.workspace.getTableName())} SET allow_read_only_guest = ${readOnlyGuestsAllowed} WHERE uuid = ${workspaceId}`
|
||||
}
|
||||
|
||||
async updateAllowGuestSignUp (workspaceId: WorkspaceUuid, guestSignUpAllowed: boolean): Promise<void> {
|
||||
await this
|
||||
.client`UPDATE ${this.client(this.workspace.getTableName())} SET allow_guest_sign_up = ${guestSignUpAllowed} WHERE uuid = ${workspaceId}`
|
||||
}
|
||||
|
||||
async assignWorkspace (accountUuid: AccountUuid, workspaceUuid: WorkspaceUuid, role: AccountRole): Promise<void> {
|
||||
await this.withRetry(
|
||||
async (rTx) =>
|
||||
|
||||
@@ -101,6 +101,7 @@ import {
|
||||
verifyPassword,
|
||||
wrap,
|
||||
updateAllowReadOnlyGuests,
|
||||
updateAllowGuestSignUp,
|
||||
getWorkspaceByDataId,
|
||||
assignableRoles,
|
||||
getWorkspacesInfoWithStatusByIds,
|
||||
@@ -2196,6 +2197,7 @@ export type AccountMethods =
|
||||
| 'getWorkspaceMembers'
|
||||
| 'updateWorkspaceRole'
|
||||
| 'updateAllowReadOnlyGuests'
|
||||
| 'updateAllowGuestSignUp'
|
||||
| 'findPersonBySocialId'
|
||||
| 'findSocialIdBySocialKey'
|
||||
| 'ensurePerson'
|
||||
@@ -2242,6 +2244,7 @@ export function getMethods (hasSignUp: boolean = true): Partial<Record<AccountMe
|
||||
deleteWorkspace: wrap(deleteWorkspace),
|
||||
updateWorkspaceRole: wrap(updateWorkspaceRole),
|
||||
updateAllowReadOnlyGuests: wrap(updateAllowReadOnlyGuests),
|
||||
updateAllowGuestSignUp: wrap(updateAllowGuestSignUp),
|
||||
createMailbox: wrap(createMailbox),
|
||||
getMailboxes: wrap(getMailboxes),
|
||||
deleteMailbox: wrap(deleteMailbox),
|
||||
|
||||
@@ -108,6 +108,7 @@ export interface Workspace {
|
||||
name: string
|
||||
url: string
|
||||
allowReadOnlyGuest: boolean
|
||||
allowGuestSignUp: boolean
|
||||
dataId?: WorkspaceDataId // Old workspace identifier. E.g. Database name in Mongo, bucket in R2, etc.
|
||||
branding?: string
|
||||
location?: Location
|
||||
@@ -209,6 +210,7 @@ export interface AccountDB {
|
||||
init: () => Promise<void>
|
||||
createWorkspace: (data: WorkspaceData, status: WorkspaceStatusData) => Promise<WorkspaceUuid>
|
||||
updateAllowReadOnlyGuests: (workspaceId: WorkspaceUuid, readOnlyGuestsAllowed: boolean) => Promise<void>
|
||||
updateAllowGuestSignUp: (workspaceId: WorkspaceUuid, guestSignUpAllowed: boolean) => Promise<void>
|
||||
assignWorkspace: (accountId: AccountUuid, workspaceId: WorkspaceUuid, role: AccountRole) => Promise<void>
|
||||
batchAssignWorkspace: (data: [AccountUuid, WorkspaceUuid, AccountRole][]) => Promise<void>
|
||||
updateWorkspaceRole: (accountId: AccountUuid, workspaceId: WorkspaceUuid, role: AccountRole) => Promise<void>
|
||||
@@ -323,6 +325,7 @@ export interface WorkspaceLoginInfo extends LoginInfo {
|
||||
workspaceDataId?: WorkspaceDataId
|
||||
endpoint: string
|
||||
role: AccountRole
|
||||
allowGuestSignUp?: boolean
|
||||
}
|
||||
|
||||
export interface OtpInfo {
|
||||
@@ -359,5 +362,5 @@ export interface AccountAggregatedInfo extends Omit<Account, 'hash' | 'salt'>, P
|
||||
uuid: AccountUuid
|
||||
integrations: Omit<Integration, 'data'>[]
|
||||
socialIds: SocialId[]
|
||||
workspaces: Omit<WorkspaceInfo, 'allowReadOnlyGuest'>[]
|
||||
workspaces: Omit<WorkspaceInfo, 'allowReadOnlyGuest' | 'allowGuestSignUp'>[]
|
||||
}
|
||||
|
||||
@@ -683,6 +683,7 @@ export async function selectWorkspace (
|
||||
workspace: workspace.uuid,
|
||||
workspaceUrl: workspace.url,
|
||||
workspaceDataId: workspace.dataId,
|
||||
allowGuestSignUp: workspace.allowReadOnlyGuest && workspace.allowGuestSignUp,
|
||||
role
|
||||
}
|
||||
}
|
||||
@@ -741,6 +742,30 @@ export async function updateAllowReadOnlyGuests (
|
||||
return { guestPerson, guestSocialIds: guestSocialIds.filter((si) => si.isDeleted !== true) }
|
||||
}
|
||||
|
||||
export async function updateAllowGuestSignUp (
|
||||
ctx: MeasureContext,
|
||||
db: AccountDB,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
params: {
|
||||
guestSignUpAllowed: boolean
|
||||
}
|
||||
): Promise<void> {
|
||||
const { guestSignUpAllowed } = params
|
||||
const { account, workspace } = decodeTokenVerbose(ctx, token)
|
||||
|
||||
if (workspace === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspaceUuid: workspace }))
|
||||
}
|
||||
|
||||
const accRole = account === systemAccountUuid ? AccountRole.Owner : await db.getWorkspaceRole(account, workspace)
|
||||
if (accRole == null || getRolePower(accRole) < getRolePower(AccountRole.Owner)) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
|
||||
await db.updateAllowGuestSignUp(workspace, guestSignUpAllowed)
|
||||
}
|
||||
|
||||
export async function updateWorkspaceRole (
|
||||
ctx: MeasureContext,
|
||||
db: AccountDB,
|
||||
@@ -883,6 +908,7 @@ export async function createWorkspaceRecord (
|
||||
createdBy: account,
|
||||
billingAccount: account,
|
||||
allowReadOnlyGuest: false,
|
||||
allowGuestSignUp: false,
|
||||
region
|
||||
},
|
||||
{
|
||||
@@ -1191,7 +1217,7 @@ export async function getWorkspaceJoinInfo (
|
||||
}
|
||||
if (workspaceUrl !== undefined && workspaceUrl !== '' && workspaceUrl !== null) {
|
||||
const workspace = await getWorkspaceByUrl(db, workspaceUrl)
|
||||
if (workspace == null || !workspace.allowReadOnlyGuest) {
|
||||
if (workspace == null || !workspace.allowReadOnlyGuest || !workspace.allowGuestSignUp) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
return {
|
||||
@@ -1221,7 +1247,7 @@ export async function doJoinByInvite (
|
||||
await db.updateWorkspaceRole(account, workspace.uuid, invite.role)
|
||||
}
|
||||
await useInvite(db, invite.id)
|
||||
} else if (workspace.allowReadOnlyGuest) {
|
||||
} else if (workspace.allowReadOnlyGuest && workspace.allowGuestSignUp) {
|
||||
if (role == null) {
|
||||
await db.assignWorkspace(account, workspace.uuid, AccountRole.Guest)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user