Use huly id for caldav, disable app secret getting for accounts (#8581)

Signed-off-by: Nikolay Chunosov <Chunosov.N@gmail.com>
This commit is contained in:
Chunosov
2025-04-16 13:39:22 +04:00
committed by GitHub
parent bd009bbc6a
commit 7015461c72
17 changed files with 138 additions and 242 deletions
+3 -2
View File
@@ -100,10 +100,11 @@
"MeetingDuration": "Trvání schůzky",
"MeetingInterval": "Pauza mezi schůzkami",
"CalDavAccess": "Přístup CalDAV",
"CalDavAccessPrompt": "Protokol CalDAV umožňuje synchronizaci vašeho osobního kalendáře Huly s externími aplikacemi kalendáře. Vyberte účet a použijte navrhované heslo pro registraci serveru Huly CalDAV v klientské aplikaci kalendáře.",
"CalDavAccessPrompt": "Protokol CalDAV umožňuje synchronizaci vašeho osobního kalendáře Huly s externími kalendářovými aplikacemi. Použijte tento účet a navrhované heslo pro registraci serveru Huly CalDAV v aplikaci kalendářového klienta.",
"CalDavAccessEnable": "Povolte přístup CalDAV k osobnímu kalendáři v pracovním prostoru {ws}",
"CalDavAccessServer": "Server CalDAV",
"CalDavAccessAccount": "Účet Huly",
"CalDavAccessPassword": "Heslo (automaticky vygenerované)"
"CalDavAccessPassword": "Heslo",
"CalDavAccessPasswordWarning": "Tohle heslo už nebudete moci znovu vidět. Uložte si ho na bezpečné místo."
}
}
+3 -2
View File
@@ -100,10 +100,11 @@
"MeetingDuration": "Meetingdauer",
"MeetingInterval": "Pause zwischen Meetings",
"CalDavAccess": "CalDAV-Zugriff",
"CalDavAccessPrompt": "Das CalDAV-Protokoll ermöglicht die Synchronisierung Ihres persönlichen Huly-Kalenders mit externen Kalenderanwendungen. Wählen Sie ein Konto aus und verwenden Sie das vorgeschlagene Passwort für die Registrierung des Huly-CalDAV-Servers in einer Kalender-Client-Anwendung.",
"CalDavAccessPrompt": "Das CalDAV-Protokoll ermöglicht die Synchronisierung Ihres persönlichen Huly-Kalenders mit externen Kalenderanwendungen. Verwenden Sie dieses Konto und das vorgeschlagene Passwort, um den Huly CalDAV-Server in einer Kalender-Client-Anwendung zu registrieren.",
"CalDavAccessEnable": "CalDAV-Zugriff auf den persönlichen Kalender im Arbeitsbereich {ws} aktivieren",
"CalDavAccessServer": "CalDAV-Server",
"CalDavAccessAccount": "Huly-Konto",
"CalDavAccessPassword": "Passwort (automatisch generiert)"
"CalDavAccessPassword": "Passwort",
"CalDavAccessPasswordWarning": "Sie können dieses Passwort nicht erneut anzeigen. Bitte speichern Sie es an einem sicheren Ort."
}
}
+3 -2
View File
@@ -100,10 +100,11 @@
"MeetingDuration": "Meeting duration",
"MeetingInterval": "Pause between meetings",
"CalDavAccess": "CalDAV access",
"CalDavAccessPrompt": "CalDAV protocol allows for syncing your personal Huly calendar with external calendar applications. Select an account and use the suggested password for registering the Huly CalDAV server in a calendar client application.",
"CalDavAccessPrompt": "CalDAV protocol allows for syncing your personal Huly calendar with external calendar applications. Use this account and the suggested password for registering the Huly CalDAV server in a calendar client application.",
"CalDavAccessEnable": "Enable CalDAV access to the personal calendar in workspace {ws}",
"CalDavAccessServer": "CalDAV server",
"CalDavAccessAccount": "Huly account",
"CalDavAccessPassword": "Password (auto-generated)"
"CalDavAccessPassword": "Password",
"CalDavAccessPasswordWarning": "You will not be able to see this password again. Please save it in a secure place."
}
}
+3 -2
View File
@@ -100,10 +100,11 @@
"MeetingDuration": "Duración de la reunión",
"MeetingInterval": "Pausa entre reuniones",
"CalDavAccess": "Acceso a CalDAV",
"CalDavAccessPrompt": "El protocolo CalDAV permite sincronizar su calendario personal de Huly con aplicaciones de calendario externas. Seleccione una cuenta y use la contraseña sugerida para registrar el servidor CalDAV de Huly en una aplicación cliente de calendario.",
"CalDavAccessPrompt": "El protocolo CalDAV permite sincronizar su calendario personal de Huly con aplicaciones de calendario externas. Utilice esta cuenta y la contraseña sugerida para registrar el servidor CalDAV de Huly en una aplicación cliente de calendario.",
"CalDavAccessEnable": "Habilite el acceso CalDAV al calendario personal en el espacio de trabajo {ws}",
"CalDavAccessServer": "Servidor CalDAV",
"CalDavAccessAccount": "Cuenta de Huly",
"CalDavAccessPassword": "Contraseña (autogenerada)"
"CalDavAccessPassword": "Contraseña",
"CalDavAccessPasswordWarning": "No podrá ver esta contraseña nuevamente. Guárdela en un lugar seguro."
}
}
+3 -2
View File
@@ -100,10 +100,11 @@
"MeetingDuration": "Durée de la réunion",
"MeetingInterval": "Pause entre les réunions",
"CalDavAccess": "Accès CalDAV",
"CalDavAccessPrompt": "Le protocole CalDAV permet de synchroniser votre calendrier personnel Huly avec des applications de calendrier externes. Sélectionnez un compte et utilisez le mot de passe suggéré pour enregistrer le serveur CalDAV Huly dans une application cliente de calendrier.",
"CalDavAccessPrompt": "Le protocole CalDAV permet de synchroniser votre calendrier personnel Huly avec des applications de calendrier externes. Utilisez ce compte et le mot de passe suggéré pour enregistrer le serveur CalDAV Huly dans une application cliente de calendrier.",
"CalDavAccessEnable": "Activer l'accès CalDAV au calendrier personnel dans l'espace de travail {ws}",
"CalDavAccessServer": "Serveur CalDAV",
"CalDavAccessAccount": "Compte Huly",
"CalDavAccessPassword": "Mot de passe (généré automatiquement)"
"CalDavAccessPassword": "Mot de passe",
"CalDavAccessPasswordWarning": "Vous ne pourrez plus voir ce mot de passe. Veuillez le conserver dans un endroit sûr."
}
}
+3 -2
View File
@@ -100,10 +100,11 @@
"MeetingDuration": "Durata riunione",
"MeetingInterval": "Pausa tra riunioni",
"CalDavAccess": "Accesso CalDAV",
"CalDavAccessPrompt": "Il protocollo CalDAV consente la sincronizzazione del calendario personale Huly con applicazioni di calendario esterne. Seleziona un account e utilizza la password suggerita per registrare il server CalDAV Huly in un'applicazione client di calendario.",
"CalDavAccessPrompt": "Il protocollo CalDAV consente la sincronizzazione del calendario personale Huly con applicazioni di calendario esterne. Utilizza questo account e la password suggerita per registrare il server CalDAV Huly in un'applicazione client di calendario.",
"CalDavAccessEnable": "Abilita l'accesso CalDAV al calendario personale nell'area di lavoro {ws}",
"CalDavAccessServer": "Server CalDAV",
"CalDavAccessAccount": "Account Huly",
"CalDavAccessPassword": "Password (generata automaticamente)"
"CalDavAccessPassword": "Password",
"CalDavAccessPasswordWarning": "Non potrai vedere di nuovo questa password. Conservala in un luogo sicuro."
}
}
+3 -2
View File
@@ -100,10 +100,11 @@
"MeetingDuration": "Duração da reunião",
"MeetingInterval": "Pausa entre reuniões",
"CalDavAccess": "CalDAV Access",
"CalDavAccessPrompt": "O protocolo CalDAV permite sincronizar o seu calendário pessoal Huly com aplicações de calendário externas. Selecione uma conta e utilize a palavra-passe sugerida para registar o servidor Huly CalDAV numa aplicação cliente de calendário.",
"CalDavAccessPrompt": "O protocolo CalDAV permite sincronizar o seu calendário pessoal Huly com aplicações de calendário externas. Utilize esta conta e a palavra-passe sugerida para registar o servidor Huly CalDAV numa aplicação cliente de calendário.",
"CalDavAccessEnable": "Ativar o acesso CalDAV ao calendário pessoal no espaço de trabalho {ws}",
"CalDavAccessServer": "Servidor CalDAV",
"CalDavAccessAccount": "Conta Huly",
"CalDavAccessPassword": "Password (gerada automaticamente)"
"CalDavAccessPassword": "Password",
"CalDavAccessPasswordWarning": "Não será possível ver esta palavra-passe novamente. Guarde-a num local seguro."
}
}
+3 -2
View File
@@ -100,10 +100,11 @@
"MeetingDuration": "Длительность встречи",
"MeetingInterval": "Перерыв между встречами",
"CalDavAccess": "Доступ через CalDAV",
"CalDavAccessPrompt": "Протокол CalDAV позволяет синхронизировать ваш личный календарь в Huly со сторонними приложениями. Выберите учетную запись и используйте предложенный пароль для регистрации сервера Huly CalDAV в календарном приложении.",
"CalDavAccessPrompt": "Протокол CalDAV позволяет синхронизировать ваш личный календарь Huly с внешними приложениями календаря. Используйте эту учетную запись и предложенный пароль для регистрации сервера Huly CalDAV в клиентском приложении календаря.",
"CalDavAccessEnable": "Разрешить доступ к личному календарю в пространстве {ws} через CalDAV",
"CalDavAccessServer": "Сервер CalDAV",
"CalDavAccessAccount": "Учетная запись Huly",
"CalDavAccessPassword": "Пароль (автоматический)"
"CalDavAccessPassword": "Пароль",
"CalDavAccessPasswordWarning": "Вы не сможете увидеть этот пароль снова. Пожалуйста, сохраните его в безопасном месте."
}
}
+3 -2
View File
@@ -100,10 +100,11 @@
"MeetingDuration": "会议时长",
"MeetingInterval": "会议间暂停",
"CalDavAccess": "CalDAV 访问",
"CalDavAccessPrompt": "CalDAV 协议允许将您的人 Huly 日历与外部日历应用同步。请选择一个帐户并使用建议的密在日历客户端应用中注册 Huly CalDav 服务器。",
"CalDavAccessPrompt": "CalDAV 協定允許將您的人 Huly 日曆與外部日曆應用程式同步。使用此帳戶和建議的密在日曆用戶端應用程式中註冊 Huly CalDAV 伺服器。",
"CalDavAccessEnable": "在工作区 {ws} 中启用 CalDAV 对个人日历的访问",
"CalDavAccessServer": "CalDAV 服务器",
"CalDavAccessAccount": "Huly 帐户",
"CalDavAccessPassword": "密码(自动生成)"
"CalDavAccessPassword": "密码",
"CalDavAccessPasswordWarning": "您将无法再次看到此密码。请将其保存在安全的地方。"
}
}
@@ -1,25 +1,16 @@
<script lang="ts">
import { Integration, IntegrationKey } from '@hcengineering/account-client'
import presentation, { getClient, getCurrentWorkspaceUrl, getCurrentWorkspaceUuid } from '@hcengineering/presentation'
import {
Dropdown,
Label,
ListItem,
Modal,
ModernEditbox,
CheckBox,
Spinner,
Button,
IconCopy,
EditBox
} from '@hcengineering/ui'
import setting from '@hcengineering/setting'
import { Integration } from '@hcengineering/account-client'
import presentation, {
copyTextToClipboard,
getCurrentWorkspaceUrl,
getCurrentWorkspaceUuid
} from '@hcengineering/presentation'
import { Label, Modal, CheckBox, Spinner, Button, IconCopy, EditBox } from '@hcengineering/ui'
import calendar from '@hcengineering/calendar'
import { createEventDispatcher, onMount } from 'svelte'
import { getMetadata, IntlString, translateCB } from '@hcengineering/platform'
import contact, { getCurrentEmployee, SocialIdentityRef } from '@hcengineering/contact'
import { buildSocialIdString, getCurrentAccount, PersonId, SocialIdType } from '@hcengineering/core'
import { calendarByIdStore, getAccountClient } from '../utils'
import { getMetadata } from '@hcengineering/platform'
import { getCurrentAccount, pickPrimarySocialId, SocialId, SocialIdType } from '@hcengineering/core'
import { getAccountClient } from '../utils'
const workspaceUuid = getCurrentWorkspaceUuid()
@@ -28,8 +19,8 @@
let loading = true
let error: string | undefined
const availableAccounts: ListItem[] = []
let selectedAccount: ListItem | undefined
let selectedSocialId: SocialId | undefined
let hulySocialId = ''
let accessEnabled = false
let serverUrl = getMetadata(calendar.metadata.CalDavServerURL)
let password = ''
@@ -40,9 +31,7 @@
$: wasAccessEnabled = integrationGlobal !== null && integrationWs !== null
$: canSave = !!(
!loading &&
((!wasAccessEnabled && accessEnabled && selectedAccount != null) ||
(wasAccessEnabled && !accessEnabled) ||
(wasAccessEnabled && accessEnabled && selectedAccount != null && selectedAccount._id !== integrationWs?.socialId))
((!wasAccessEnabled && accessEnabled && selectedSocialId != null) || (wasAccessEnabled && !accessEnabled))
)
function generateRandomPassword (length = 24): string {
@@ -60,70 +49,69 @@
try {
password = generateRandomPassword()
const account = getCurrentAccount()
const accountClient = getAccountClient()
const socialId = pickPrimarySocialId(getCurrentAccount().fullSocialIds)
if (socialId.type !== SocialIdType.HULY) {
// Thid should not happen, no need to translate
error = 'Appropriate account not found'
return
}
const wsId = getCurrentWorkspaceUuid()
for (const socialId of account.fullSocialIds) {
// TODO: Handle delete social ids generically, we don't have isDelete field on thr workspace side
if (socialId.type === SocialIdType.EMAIL && socialId.value.includes('#')) {
continue
}
if (availableAccounts.find((a) => a.label === socialId.value) == null) {
availableAccounts.push({
_id: socialId._id,
label: socialId.value
})
}
const integrations = await accountClient.listIntegrations({
socialId: socialId._id,
kind: 'caldav'
})
for (const integration of integrations) {
if (integration.workspaceUuid == null) {
if (integrationGlobal == null) {
integrationGlobal = integration
continue
}
}
totalWsIntegrations += 1
if (integration.workspaceUuid === wsId && integrationWs == null) {
integrationWs = integration
selectedAccount = {
_id: socialId._id,
label: socialId.value
}
hulySocialId = socialId.value
const accountClient = getAccountClient()
const integrations = await accountClient.listIntegrations({
socialId: socialId._id,
kind: 'caldav'
})
for (const integration of integrations) {
if (integration.workspaceUuid == null) {
if (integrationGlobal == null) {
integrationGlobal = integration
continue
}
}
totalWsIntegrations += 1
if (integration.workspaceUuid === wsId && integrationWs == null) {
integrationWs = integration
}
}
selectedSocialId = socialId
accessEnabled = integrationWs != null && integrationGlobal != null
} catch (err: any) {
console.error('Failed to load CalDAV intergrations', err)
error = `${err.message ?? 'Unable to load CalDAV intergrations'}`
} finally {
loading = false
}
loading = false
})
async function save (): Promise<void> {
loading = true
try {
if (selectedSocialId == null) {
return
}
const accountClient = getAccountClient()
const socialId = selectedSocialId._id
const kind = 'caldav'
const key = 'password'
if (wasAccessEnabled && !accessEnabled) {
// Access disabled
await accountClient.deleteIntegration({
socialId: selectedAccount?._id as PersonId,
kind: 'caldav',
socialId,
kind,
workspaceUuid
})
if (totalWsIntegrations === 1 && integrationGlobal != null) {
await accountClient.deleteIntegrationSecret({
socialId: integrationGlobal.socialId,
kind: 'caldav',
socialId,
kind,
workspaceUuid: null,
key: 'caldav'
key
})
await accountClient.deleteIntegration({
socialId: integrationGlobal.socialId,
kind: 'caldav',
socialId,
kind,
workspaceUuid: null
})
}
@@ -131,46 +119,21 @@
// Access enabled
if (integrationGlobal === null) {
await accountClient.createIntegration({
socialId: selectedAccount?._id as PersonId,
kind: 'caldav',
workspaceUuid: null
})
integrationGlobal = await accountClient.getIntegration({
socialId: selectedAccount?._id as PersonId,
kind: 'caldav',
socialId,
kind,
workspaceUuid: null
})
}
const secret = await accountClient.getIntegrationSecret({
socialId: integrationGlobal?.socialId as PersonId,
kind: 'caldav',
await accountClient.addIntegrationSecret({
socialId,
kind,
workspaceUuid: null,
key: 'caldav'
})
if (secret == null) {
await accountClient.addIntegrationSecret({
socialId: integrationGlobal?.socialId as PersonId,
kind: 'caldav',
workspaceUuid: null,
key: 'caldav',
secret: password
})
}
await accountClient.createIntegration({
socialId: selectedAccount?._id as PersonId,
kind: 'caldav',
workspaceUuid
})
} else if (accessEnabled && wasAccessEnabled && selectedAccount?._id !== integrationWs?.socialId) {
// Access changed
await accountClient.deleteIntegration({
socialId: integrationWs?.socialId as PersonId,
kind: 'caldav',
workspaceUuid
key,
secret: password
})
await accountClient.createIntegration({
socialId: selectedAccount?._id as PersonId,
kind: 'caldav',
socialId,
kind,
workspaceUuid
})
}
@@ -178,42 +141,21 @@
} catch (err: any) {
error = `${err.message ?? 'Unable to save CalDAV intergration'}`
console.error('Failed to save CalDAV integration', err)
} finally {
loading = false
}
loading = false
}
async function copyServer (): Promise<void> {
console.log(serverUrl)
await navigator?.clipboard?.writeText(serverUrl ?? '')
await copyTextToClipboard(serverUrl ?? '')
}
async function copyAccount (): Promise<void> {
console.log(selectedAccount?.label)
await navigator?.clipboard?.writeText(selectedAccount?.label ?? '')
await copyTextToClipboard(selectedSocialId?.value ?? '')
}
async function copyPassword (): Promise<void> {
loading = true
try {
if (integrationGlobal != null) {
const accountClient = getAccountClient()
const secret = await accountClient.getIntegrationSecret({
socialId: integrationGlobal.socialId,
kind: 'caldav',
workspaceUuid: null,
key: 'caldav'
})
if (secret != null) {
await navigator?.clipboard?.writeText(secret.secret)
return
}
}
await navigator?.clipboard?.writeText(password)
} catch (err: any) {
error = `${err.message ?? 'Unable to copy password'}`
console.error('Failed to copy password', err)
}
loading = false
await copyTextToClipboard(password)
}
</script>
@@ -267,19 +209,7 @@
<div class="flex-col-stretch flex-gap-1-5" class:accessDisabled={!accessEnabled}>
<Label label={calendar.string.CalDavAccessAccount} />
<div class="flex-row-center flex-gap-1">
<Dropdown
size="large"
placeholder={calendar.string.CalDavAccessAccount}
items={availableAccounts}
selected={selectedAccount}
justify="left"
withSearch={false}
stretchWidth={true}
disabled={!accessEnabled}
on:selected={(e) => {
selectedAccount = e.detail
}}
/>
<EditBox bind:value={hulySocialId} kind="ghost" fullSize focusable disabled={true} />
<Button
icon={IconCopy}
size="small"
@@ -291,20 +221,25 @@
</div>
</div>
<div class="flex-col-stretch flex-gap-1-5" class:accessDisabled={!accessEnabled}>
<Label label={calendar.string.CalDavAccessPassword} />
<div class="flex-row-center flex-gap-1">
<EditBox bind:value={password} kind="ghost" format="password" fullSize focusable disabled={true} />
<Button
icon={IconCopy}
size="small"
kind="ghost"
disabled={!accessEnabled}
showTooltip={{ label: presentation.string.Copy }}
on:click={copyPassword}
/>
{#if !wasAccessEnabled}
<div class="flex-col-stretch flex-gap-1-5" class:accessDisabled={!accessEnabled}>
<Label label={calendar.string.CalDavAccessPassword} />
<div class="flex-row-center flex-gap-1">
<EditBox bind:value={password} kind="ghost" format="password" fullSize focusable disabled={true} />
<Button
icon={IconCopy}
size="small"
kind="ghost"
disabled={!accessEnabled}
showTooltip={{ label: presentation.string.Copy }}
on:click={copyPassword}
/>
</div>
{#if canSave}
<Label label={calendar.string.CalDavAccessPasswordWarning} />
{/if}
</div>
</div>
{/if}
{#if error}
<div style="color: var(--theme-error-color)">{error}</div>
@@ -214,9 +214,6 @@
readonly={readOnly}
placeholder={calendar.string.Description}
bind:content={description}
on:changeSize={(e) => {
console.log('ChangeSize', e)
}}
/>
</div>
<div class="divider" />
+1 -1
View File
@@ -246,7 +246,7 @@ const calendarPlugin = plugin(calendarId, {
CalDavAccessServer: '' as IntlString,
CalDavAccessAccount: '' as IntlString,
CalDavAccessPassword: '' as IntlString,
CopyValue: '' as IntlString
CalDavAccessPasswordWarning: '' as IntlString
},
handler: {
DisconnectHandler: '' as Handler
-2
View File
@@ -133,7 +133,6 @@ export function followeeDataUnsubscribe (topic: string, handler: (data: any) =>
}
export function toggleFollowee (person: Ref<Person> | undefined): void {
console.log('toggle followee', person)
followee.update((p) => (p === person ? undefined : person))
const f = get(followee)
@@ -150,7 +149,6 @@ export function toggleFollowee (person: Ref<Person> | undefined): void {
}
}
} else {
console.log('no followee')
for (const handlers of followeeDataHandlers.values()) {
for (const handler of handlers) {
handler(undefined)
@@ -1248,7 +1248,6 @@ describe('integration methods', () => {
})
describe('getIntegrationSecret', () => {
const mockAccount = 'test-account'
const mockSocialId = 'test-social-id' as PersonId
const mockSecretKey: IntegrationSecretKey = {
socialId: mockSocialId,
@@ -1267,56 +1266,11 @@ describe('integration methods', () => {
extra: { service: 'github' }
})
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue(mockSecret)
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
;(mockDb.workspace.findOne as jest.Mock).mockResolvedValue({ uuid: 'test-workspace' })
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue({})
const result = await getIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecretKey)
expect(result).toEqual(mockSecret)
expect(mockDb.integrationSecret.findOne).toHaveBeenCalledWith(mockSecretKey)
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({ _id: mockSocialId, verifiedOn: { $gt: 0 } })
})
test('should allow verified user to get their secret', async () => {
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
account: mockAccount
})
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue(mockSecret)
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
;(mockDb.workspace.findOne as jest.Mock).mockResolvedValue({ uuid: 'test-workspace' })
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue({})
const result = await getIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecretKey)
expect(result).toEqual(mockSecret)
expect(mockDb.integrationSecret.findOne).toHaveBeenCalledWith(mockSecretKey)
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({
_id: mockSocialId,
personUuid: mockAccount,
verifiedOn: { $gt: 0 }
})
})
test('should throw error when user gets secret for different account', async () => {
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
account: mockAccount
})
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue(mockSecret)
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue(null)
;(mockDb.workspace.findOne as jest.Mock).mockResolvedValue({ uuid: 'test-workspace' })
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue({})
await expect(getIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecretKey)).rejects.toThrow(
new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
)
expect(mockDb.integrationSecret.findOne).not.toHaveBeenCalledWith()
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({
_id: mockSocialId,
personUuid: mockAccount,
verifiedOn: { $gt: 0 }
})
})
test('should return null when integration secret not found', async () => {
+2 -1
View File
@@ -1187,9 +1187,10 @@ export async function getWorkspacesInfo (
db: AccountDB,
branding: Branding | null,
token: string,
ids: WorkspaceUuid[]
params: { ids: WorkspaceUuid[] }
): Promise<WorkspaceInfoWithStatus[]> {
const { account } = decodeTokenVerbose(ctx, token)
const { ids } = params
if (account !== systemAccountUuid) {
ctx.error('getWorkspaceInfos with wrong user', { account, token })
+23 -21
View File
@@ -701,12 +701,16 @@ export async function addIntegrationSecret (
params: IntegrationSecret
): Promise<void> {
const { extra, account } = decodeTokenVerbose(ctx, token)
const { socialId, kind, workspaceUuid, key, secret } = params
if (kind == null || socialId == null || workspaceUuid === undefined || key == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
}
const existingIntegration = await findExistingIntegration(account, db, params, extra)
if (existingIntegration == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
}
const { socialId, kind, workspaceUuid, key, secret } = params
const secretKey: IntegrationSecretKey = { socialId, kind, workspaceUuid, key }
const existingSecret = await db.integrationSecret.findOne(secretKey)
if (existingSecret != null) {
@@ -724,12 +728,16 @@ export async function updateIntegrationSecret (
params: IntegrationSecret
): Promise<void> {
const { extra, account } = decodeTokenVerbose(ctx, token)
const { socialId, kind, workspaceUuid, key, secret } = params
if (kind == null || socialId == null || workspaceUuid === undefined || key == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
}
const existingIntegration = await findExistingIntegration(account, db, params, extra)
if (existingIntegration == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
}
const { socialId, kind, workspaceUuid, key, secret } = params
const secretKey: IntegrationSecretKey = { socialId, kind, workspaceUuid, key }
const existingSecret = await db.integrationSecret.findOne(secretKey)
if (existingSecret == null) {
@@ -747,12 +755,16 @@ export async function deleteIntegrationSecret (
params: IntegrationSecretKey
): Promise<void> {
const { extra, account } = decodeTokenVerbose(ctx, token)
const { socialId, kind, workspaceUuid, key } = params
if (kind == null || socialId == null || workspaceUuid === undefined || key == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
}
const existingIntegration = await findExistingIntegration(account, db, params, extra)
if (existingIntegration == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
}
const { socialId, kind, workspaceUuid, key } = params
const secretKey: IntegrationSecretKey = { socialId, kind, workspaceUuid, key }
const existingSecret = await db.integrationSecret.findOne(secretKey)
if (existingSecret == null) {
@@ -769,13 +781,13 @@ export async function getIntegrationSecret (
token: string,
params: IntegrationSecretKey
): Promise<IntegrationSecret | null> {
const { extra, account } = decodeTokenVerbose(ctx, token)
const existingIntegration = await findExistingIntegration(account, db, params, extra)
if (existingIntegration == null) {
return null
}
const { extra } = decodeTokenVerbose(ctx, token)
verifyAllowedServices(integrationServices, extra)
const { socialId, kind, workspaceUuid, key } = params
if (kind == null || socialId == null || workspaceUuid === undefined || key == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
}
const existing = await db.integrationSecret.findOne({ socialId, kind, workspaceUuid, key })
return existing
@@ -788,20 +800,10 @@ export async function listIntegrationsSecrets (
token: string,
params: Partial<IntegrationSecretKey>
): Promise<IntegrationSecret[]> {
const { extra, account } = decodeTokenVerbose(ctx, token)
const { extra } = decodeTokenVerbose(ctx, token)
verifyAllowedServices(integrationServices, extra)
const { socialId, kind, workspaceUuid, key } = params
if (!verifyAllowedServices(integrationServices, extra, false)) {
if (socialId != null) {
const existingSocialId = await db.socialId.findOne({ _id: socialId, personUuid: account, verifiedOn: { $gt: 0 } })
if (existingSocialId == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
}
} else {
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
}
}
return await db.integrationSecret.find({ socialId, kind, workspaceUuid, key })
}
+1 -1
View File
@@ -1439,7 +1439,7 @@ export async function setTimezoneIfNotDefined (
}
// Move to config?
export const integrationServices = ['github', 'telegram-bot', 'telegram', 'mailbox']
export const integrationServices = ['github', 'telegram-bot', 'telegram', 'mailbox', 'caldav']
export async function findExistingIntegration (
account: AccountUuid,