mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-11 18:01:59 +00:00
allow user to manage their integrations (#8545)
Signed-off-by: Nikolay Chunosov <Chunosov.N@gmail.com>
This commit is contained in:
parent
6cc278549c
commit
241ee3be06
@ -226,9 +226,68 @@ describe('integration methods', () => {
|
|||||||
|
|
||||||
await createIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegration)
|
await createIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegration)
|
||||||
|
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({ _id: mockSocialId, verifiedOn: { $gt: 0 } })
|
||||||
expect(mockDb.integration.insertOne).toHaveBeenCalledWith(mockIntegration)
|
expect(mockDb.integration.insertOne).toHaveBeenCalledWith(mockIntegration)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should allow verified user to create their integration', async () => {
|
||||||
|
const mockAccount = 'test-account'
|
||||||
|
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||||
|
account: mockAccount
|
||||||
|
})
|
||||||
|
|
||||||
|
const mockSocialId = 'test-social-id' as PersonId
|
||||||
|
const mockIntegration: Integration = {
|
||||||
|
socialId: mockSocialId,
|
||||||
|
kind: 'test-kind',
|
||||||
|
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
||||||
|
data: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
|
||||||
|
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
;(mockDb.workspace.findOne as jest.Mock).mockResolvedValue({ uuid: 'test-workspace' })
|
||||||
|
|
||||||
|
await createIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegration)
|
||||||
|
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: mockSocialId,
|
||||||
|
personUuid: mockAccount,
|
||||||
|
verifiedOn: { $gt: 0 }
|
||||||
|
})
|
||||||
|
expect(mockDb.integration.insertOne).toHaveBeenCalledWith(mockIntegration)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should throw error when user creates integration for different social id', async () => {
|
||||||
|
const mockAccount = 'test-account'
|
||||||
|
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||||
|
account: mockAccount
|
||||||
|
})
|
||||||
|
|
||||||
|
const mockSocialId = 'test-social-id' as PersonId
|
||||||
|
const mockIntegration: Integration = {
|
||||||
|
socialId: mockSocialId,
|
||||||
|
kind: 'test-kind',
|
||||||
|
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
||||||
|
data: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
;(mockDb.workspace.findOne as jest.Mock).mockResolvedValue({ uuid: 'test-workspace' })
|
||||||
|
|
||||||
|
await expect(createIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegration)).rejects.toThrow(
|
||||||
|
new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: mockSocialId,
|
||||||
|
personUuid: mockAccount,
|
||||||
|
verifiedOn: { $gt: 0 }
|
||||||
|
})
|
||||||
|
expect(mockDb.integration.insertOne).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
test('should throw error when social id not found', async () => {
|
test('should throw error when social id not found', async () => {
|
||||||
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||||
extra: { service: 'github' }
|
extra: { service: 'github' }
|
||||||
@ -322,8 +381,10 @@ describe('integration methods', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('updateIntegration', () => {
|
describe('updateIntegration', () => {
|
||||||
|
const mockAccount = 'test-account'
|
||||||
|
const mockSocialId = 'test-social-id' as PersonId
|
||||||
const mockIntegration: Integration = {
|
const mockIntegration: Integration = {
|
||||||
socialId: 'test-social-id' as PersonId,
|
socialId: mockSocialId,
|
||||||
kind: 'test-kind',
|
kind: 'test-kind',
|
||||||
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
||||||
data: { someData: 'value' }
|
data: { someData: 'value' }
|
||||||
@ -337,6 +398,8 @@ describe('integration methods', () => {
|
|||||||
...mockIntegration,
|
...mockIntegration,
|
||||||
data: { oldData: 'old' }
|
data: { oldData: 'old' }
|
||||||
})
|
})
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
|
||||||
|
;(mockDb.workspace.findOne as jest.Mock).mockResolvedValue({ uuid: 'test-workspace' })
|
||||||
|
|
||||||
await updateIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegration)
|
await updateIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegration)
|
||||||
|
|
||||||
@ -348,6 +411,60 @@ describe('integration methods', () => {
|
|||||||
},
|
},
|
||||||
{ data: mockIntegration.data }
|
{ data: mockIntegration.data }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({ _id: mockSocialId, verifiedOn: { $gt: 0 } })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should allow verified user to update their integration', async () => {
|
||||||
|
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||||
|
account: mockAccount
|
||||||
|
})
|
||||||
|
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue({
|
||||||
|
...mockIntegration,
|
||||||
|
data: { oldData: 'old' }
|
||||||
|
})
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
|
||||||
|
;(mockDb.workspace.findOne as jest.Mock).mockResolvedValue({ uuid: 'test-workspace' })
|
||||||
|
|
||||||
|
await updateIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegration)
|
||||||
|
|
||||||
|
expect(mockDb.integration.updateOne).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
socialId: mockIntegration.socialId,
|
||||||
|
kind: mockIntegration.kind,
|
||||||
|
workspaceUuid: mockIntegration.workspaceUuid
|
||||||
|
},
|
||||||
|
{ data: mockIntegration.data }
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: mockSocialId,
|
||||||
|
personUuid: mockAccount,
|
||||||
|
verifiedOn: { $gt: 0 }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should throw error when use updates integration for different social id', async () => {
|
||||||
|
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||||
|
account: mockAccount
|
||||||
|
})
|
||||||
|
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue({
|
||||||
|
...mockIntegration,
|
||||||
|
data: { oldData: 'old' }
|
||||||
|
})
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
|
||||||
|
await expect(updateIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegration)).rejects.toThrow(
|
||||||
|
new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(mockDb.integration.updateOne).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: mockSocialId,
|
||||||
|
personUuid: mockAccount,
|
||||||
|
verifiedOn: { $gt: 0 }
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should throw error when integration not found', async () => {
|
test('should throw error when integration not found', async () => {
|
||||||
@ -355,6 +472,7 @@ describe('integration methods', () => {
|
|||||||
extra: { service: 'github' }
|
extra: { service: 'github' }
|
||||||
})
|
})
|
||||||
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue(null)
|
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
|
||||||
|
|
||||||
await expect(updateIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegration)).rejects.toThrow(
|
await expect(updateIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegration)).rejects.toThrow(
|
||||||
new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
|
new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
|
||||||
@ -377,8 +495,10 @@ describe('integration methods', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('deleteIntegration', () => {
|
describe('deleteIntegration', () => {
|
||||||
|
const mockAccount = 'test-account'
|
||||||
|
const mockSocialId = 'test-social-id' as PersonId
|
||||||
const mockIntegrationKey: IntegrationKey = {
|
const mockIntegrationKey: IntegrationKey = {
|
||||||
socialId: 'test-social-id' as PersonId,
|
socialId: mockSocialId,
|
||||||
kind: 'test-kind',
|
kind: 'test-kind',
|
||||||
workspaceUuid: 'test-workspace' as WorkspaceUuid
|
workspaceUuid: 'test-workspace' as WorkspaceUuid
|
||||||
}
|
}
|
||||||
@ -391,10 +511,57 @@ describe('integration methods', () => {
|
|||||||
...mockIntegrationKey,
|
...mockIntegrationKey,
|
||||||
data: {}
|
data: {}
|
||||||
})
|
})
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
|
||||||
|
;(mockDb.workspace.findOne as jest.Mock).mockResolvedValue({ uuid: 'test-workspace' })
|
||||||
|
|
||||||
await deleteIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegrationKey)
|
await deleteIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegrationKey)
|
||||||
|
|
||||||
expect(mockDb.integration.deleteMany).toHaveBeenCalledWith(mockIntegrationKey)
|
expect(mockDb.integration.deleteMany).toHaveBeenCalledWith(mockIntegrationKey)
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({ _id: mockSocialId, verifiedOn: { $gt: 0 } })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should allow verified user to delete their integration', async () => {
|
||||||
|
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||||
|
account: mockAccount
|
||||||
|
})
|
||||||
|
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue({
|
||||||
|
...mockIntegrationKey,
|
||||||
|
data: {}
|
||||||
|
})
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
|
||||||
|
;(mockDb.workspace.findOne as jest.Mock).mockResolvedValue({ uuid: 'test-workspace' })
|
||||||
|
|
||||||
|
await deleteIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegrationKey)
|
||||||
|
|
||||||
|
expect(mockDb.integration.deleteMany).toHaveBeenCalledWith(mockIntegrationKey)
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: mockSocialId,
|
||||||
|
personUuid: mockAccount,
|
||||||
|
verifiedOn: { $gt: 0 }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should throw error when user deletes integration for different social id', async () => {
|
||||||
|
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||||
|
account: mockAccount
|
||||||
|
})
|
||||||
|
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue({
|
||||||
|
...mockIntegrationKey,
|
||||||
|
data: {}
|
||||||
|
})
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
;(mockDb.workspace.findOne as jest.Mock).mockResolvedValue({ uuid: 'test-workspace' })
|
||||||
|
|
||||||
|
await expect(deleteIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegrationKey)).rejects.toThrow(
|
||||||
|
new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(mockDb.integration.deleteMany).not.toHaveBeenCalled()
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: mockSocialId,
|
||||||
|
personUuid: mockAccount,
|
||||||
|
verifiedOn: { $gt: 0 }
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should throw error when integration not found', async () => {
|
test('should throw error when integration not found', async () => {
|
||||||
@ -402,6 +569,7 @@ describe('integration methods', () => {
|
|||||||
extra: { service: 'github' }
|
extra: { service: 'github' }
|
||||||
})
|
})
|
||||||
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue(null)
|
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
|
||||||
|
|
||||||
await expect(deleteIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegrationKey)).rejects.toThrow(
|
await expect(deleteIntegration(mockCtx, mockDb, mockBranding, mockToken, mockIntegrationKey)).rejects.toThrow(
|
||||||
new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
|
new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
|
||||||
@ -424,15 +592,17 @@ describe('integration methods', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('getIntegration', () => {
|
describe('getIntegration', () => {
|
||||||
|
const mockAccount = 'test-account'
|
||||||
|
const mockSocialId = 'test-social-id' as PersonId
|
||||||
const mockKey: IntegrationKey = {
|
const mockKey: IntegrationKey = {
|
||||||
socialId: 'test-social-id' as PersonId,
|
socialId: mockSocialId,
|
||||||
kind: 'test-kind',
|
kind: 'test-kind',
|
||||||
workspaceUuid: 'test-workspace' as WorkspaceUuid
|
workspaceUuid: 'test-workspace' as WorkspaceUuid
|
||||||
}
|
}
|
||||||
|
|
||||||
test('should allow verified user to get their integration', async () => {
|
test('should allow verified user to get their integration', async () => {
|
||||||
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||||
account: 'test-account',
|
account: mockAccount,
|
||||||
extra: {}
|
extra: {}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -442,13 +612,19 @@ describe('integration methods', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
;(mockDb.socialId.find as jest.Mock).mockResolvedValue([
|
;(mockDb.socialId.find as jest.Mock).mockResolvedValue([
|
||||||
{ _id: mockKey.socialId, personUuid: 'test-account', verifiedOn: 1 }
|
{ _id: mockKey.socialId, personUuid: mockAccount, verifiedOn: 1 }
|
||||||
])
|
])
|
||||||
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue(mockIntegration)
|
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue(mockIntegration)
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
|
||||||
|
|
||||||
const result = await getIntegration(mockCtx, mockDb, mockBranding, mockToken, mockKey)
|
const result = await getIntegration(mockCtx, mockDb, mockBranding, mockToken, mockKey)
|
||||||
expect(result).toEqual(mockIntegration)
|
expect(result).toEqual(mockIntegration)
|
||||||
expect(mockDb.integration.findOne).toHaveBeenCalledWith(mockKey)
|
expect(mockDb.integration.findOne).toHaveBeenCalledWith(mockKey)
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: mockSocialId,
|
||||||
|
personUuid: mockAccount,
|
||||||
|
verifiedOn: { $gt: 0 }
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should throw error when there is no matching verified social id', async () => {
|
test('should throw error when there is no matching verified social id', async () => {
|
||||||
@ -678,8 +854,9 @@ describe('integration methods', () => {
|
|||||||
extra: { service }
|
extra: { service }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const mockSocialId = 'test-social-id' as PersonId
|
||||||
const mockSecret: IntegrationSecret = {
|
const mockSecret: IntegrationSecret = {
|
||||||
socialId: 'test-social-id' as PersonId,
|
socialId: mockSocialId,
|
||||||
kind: 'test-kind',
|
kind: 'test-kind',
|
||||||
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
||||||
key: 'test-key',
|
key: 'test-key',
|
||||||
@ -695,20 +872,101 @@ describe('integration methods', () => {
|
|||||||
|
|
||||||
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue(mockIntegration)
|
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue(mockIntegration)
|
||||||
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue(null)
|
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
|
||||||
|
;(mockDb.workspace.findOne as jest.Mock).mockResolvedValue({ uuid: 'test-workspace' })
|
||||||
|
|
||||||
await addIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecret)
|
await addIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecret)
|
||||||
|
|
||||||
expect(mockDb.integrationSecret.insertOne).toHaveBeenCalledWith(mockSecret)
|
expect(mockDb.integrationSecret.insertOne).toHaveBeenCalledWith(mockSecret)
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({ _id: mockSocialId, verifiedOn: { $gt: 0 } })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should allow verified user services to create their integration secret', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
const mockAccount = 'test-account'
|
||||||
|
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||||
|
account: mockAccount
|
||||||
|
})
|
||||||
|
|
||||||
|
const mockSocialId = 'test-social-id' as PersonId
|
||||||
|
const mockSecret: IntegrationSecret = {
|
||||||
|
socialId: mockSocialId,
|
||||||
|
kind: 'test-kind',
|
||||||
|
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
||||||
|
key: 'test-key',
|
||||||
|
secret: 'test-secret'
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockIntegration: Integration = {
|
||||||
|
socialId: mockSecret.socialId,
|
||||||
|
kind: mockSecret.kind,
|
||||||
|
workspaceUuid: mockSecret.workspaceUuid,
|
||||||
|
data: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue(mockIntegration)
|
||||||
|
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
|
||||||
|
;(mockDb.workspace.findOne as jest.Mock).mockResolvedValue({ uuid: 'test-workspace' })
|
||||||
|
|
||||||
|
await addIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecret)
|
||||||
|
|
||||||
|
expect(mockDb.integrationSecret.insertOne).toHaveBeenCalledWith(mockSecret)
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: mockSocialId,
|
||||||
|
personUuid: mockAccount,
|
||||||
|
verifiedOn: { $gt: 0 }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should throw error when user services adds integration secret for different social id', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
const mockAccount = 'test-account'
|
||||||
|
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||||
|
account: mockAccount
|
||||||
|
})
|
||||||
|
|
||||||
|
const mockSocialId = 'test-social-id' as PersonId
|
||||||
|
const mockSecret: IntegrationSecret = {
|
||||||
|
socialId: mockSocialId,
|
||||||
|
kind: 'test-kind',
|
||||||
|
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
||||||
|
key: 'test-key',
|
||||||
|
secret: 'test-secret'
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockIntegration: Integration = {
|
||||||
|
socialId: mockSecret.socialId,
|
||||||
|
kind: mockSecret.kind,
|
||||||
|
workspaceUuid: mockSecret.workspaceUuid,
|
||||||
|
data: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue(mockIntegration)
|
||||||
|
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
|
||||||
|
await expect(addIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecret)).rejects.toThrow(
|
||||||
|
new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(mockDb.integrationSecret.insertOne).not.toHaveBeenCalled()
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: mockSocialId,
|
||||||
|
personUuid: mockAccount,
|
||||||
|
verifiedOn: { $gt: 0 }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('should throw error when integration does not exist', async () => {
|
test('should throw error when integration does not exist', async () => {
|
||||||
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||||
extra: { service: 'github' }
|
extra: { service: 'github' }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const mockSocialId = 'test-social-id' as PersonId
|
||||||
const mockSecret: IntegrationSecret = {
|
const mockSecret: IntegrationSecret = {
|
||||||
socialId: 'test-social-id' as PersonId,
|
socialId: mockSocialId,
|
||||||
kind: 'test-kind',
|
kind: 'test-kind',
|
||||||
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
||||||
key: 'test-key',
|
key: 'test-key',
|
||||||
@ -716,12 +974,15 @@ describe('integration methods', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue(null)
|
;(mockDb.integration.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
|
||||||
|
;(mockDb.workspace.findOne as jest.Mock).mockResolvedValue({ uuid: 'test-workspace' })
|
||||||
|
|
||||||
await expect(addIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecret)).rejects.toThrow(
|
await expect(addIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecret)).rejects.toThrow(
|
||||||
new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
|
new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(mockDb.integrationSecret.insertOne).not.toHaveBeenCalled()
|
expect(mockDb.integrationSecret.insertOne).not.toHaveBeenCalled()
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({ _id: mockSocialId, verifiedOn: { $gt: 0 } })
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should throw error if secret already exists', async () => {
|
test('should throw error if secret already exists', async () => {
|
||||||
@ -776,8 +1037,10 @@ describe('integration methods', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('updateIntegrationSecret', () => {
|
describe('updateIntegrationSecret', () => {
|
||||||
|
const mockAccount = 'test-account'
|
||||||
|
const mockSocialId = 'test-social-id' as PersonId
|
||||||
const mockSecret: IntegrationSecret = {
|
const mockSecret: IntegrationSecret = {
|
||||||
socialId: 'test-social-id' as PersonId,
|
socialId: mockSocialId,
|
||||||
kind: 'test-kind',
|
kind: 'test-kind',
|
||||||
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
||||||
key: 'test-key',
|
key: 'test-key',
|
||||||
@ -799,10 +1062,59 @@ describe('integration methods', () => {
|
|||||||
...mockSecret,
|
...mockSecret,
|
||||||
secret: 'old-secret'
|
secret: 'old-secret'
|
||||||
})
|
})
|
||||||
|
;(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({})
|
||||||
|
|
||||||
await updateIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecret)
|
await updateIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecret)
|
||||||
|
|
||||||
expect(mockDb.integrationSecret.updateOne).toHaveBeenCalledWith(mockSecretKey, { secret: mockSecret.secret })
|
expect(mockDb.integrationSecret.updateOne).toHaveBeenCalledWith(mockSecretKey, { secret: mockSecret.secret })
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({ _id: mockSocialId, verifiedOn: { $gt: 0 } })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should allow verified user to update their secret', async () => {
|
||||||
|
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||||
|
account: mockAccount
|
||||||
|
})
|
||||||
|
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue({
|
||||||
|
...mockSecret,
|
||||||
|
secret: 'old-secret'
|
||||||
|
})
|
||||||
|
;(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({})
|
||||||
|
|
||||||
|
await updateIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecret)
|
||||||
|
|
||||||
|
expect(mockDb.integrationSecret.updateOne).toHaveBeenCalledWith(mockSecretKey, { secret: mockSecret.secret })
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: mockSocialId,
|
||||||
|
personUuid: mockAccount,
|
||||||
|
verifiedOn: { $gt: 0 }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should throw error when user updates secret for different social id', async () => {
|
||||||
|
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||||
|
account: mockAccount
|
||||||
|
})
|
||||||
|
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue({
|
||||||
|
...mockSecret,
|
||||||
|
secret: 'old-secret'
|
||||||
|
})
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
;(mockDb.workspace.findOne as jest.Mock).mockResolvedValue({ uuid: 'test-workspace' })
|
||||||
|
|
||||||
|
await expect(updateIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecret)).rejects.toThrow(
|
||||||
|
new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(mockDb.integrationSecret.updateOne).not.toHaveBeenCalledWith()
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: mockSocialId,
|
||||||
|
personUuid: mockAccount,
|
||||||
|
verifiedOn: { $gt: 0 }
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should throw error when secret not found', async () => {
|
test('should throw error when secret not found', async () => {
|
||||||
@ -810,6 +1122,7 @@ describe('integration methods', () => {
|
|||||||
extra: { service: 'github' }
|
extra: { service: 'github' }
|
||||||
})
|
})
|
||||||
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue(null)
|
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
|
||||||
|
|
||||||
await expect(updateIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecret)).rejects.toThrow(
|
await expect(updateIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecret)).rejects.toThrow(
|
||||||
new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationSecretNotFound, {}))
|
new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationSecretNotFound, {}))
|
||||||
@ -833,8 +1146,10 @@ describe('integration methods', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('deleteIntegrationSecret', () => {
|
describe('deleteIntegrationSecret', () => {
|
||||||
|
const mockAccount = 'test-account'
|
||||||
|
const mockSocialId = 'test-social-id' as PersonId
|
||||||
const mockSecretKey: IntegrationSecretKey = {
|
const mockSecretKey: IntegrationSecretKey = {
|
||||||
socialId: 'test-social-id' as PersonId,
|
socialId: mockSocialId,
|
||||||
kind: 'test-kind',
|
kind: 'test-kind',
|
||||||
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
||||||
key: 'test-key'
|
key: 'test-key'
|
||||||
@ -848,10 +1163,60 @@ describe('integration methods', () => {
|
|||||||
...mockSecretKey,
|
...mockSecretKey,
|
||||||
secret: 'test-secret'
|
secret: 'test-secret'
|
||||||
})
|
})
|
||||||
|
;(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({})
|
||||||
|
|
||||||
await deleteIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecretKey)
|
await deleteIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecretKey)
|
||||||
|
|
||||||
expect(mockDb.integrationSecret.deleteMany).toHaveBeenCalledWith(mockSecretKey)
|
expect(mockDb.integrationSecret.deleteMany).toHaveBeenCalledWith(mockSecretKey)
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({ _id: mockSocialId, verifiedOn: { $gt: 0 } })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should allow verified user to delete their secret', async () => {
|
||||||
|
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||||
|
account: mockAccount
|
||||||
|
})
|
||||||
|
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue({
|
||||||
|
...mockSecretKey,
|
||||||
|
secret: 'test-secret'
|
||||||
|
})
|
||||||
|
;(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({})
|
||||||
|
|
||||||
|
await deleteIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecretKey)
|
||||||
|
|
||||||
|
expect(mockDb.integrationSecret.deleteMany).toHaveBeenCalledWith(mockSecretKey)
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: mockSocialId,
|
||||||
|
personUuid: mockAccount,
|
||||||
|
verifiedOn: { $gt: 0 }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should throw error when user deletes secret for different social id', async () => {
|
||||||
|
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||||
|
account: mockAccount
|
||||||
|
})
|
||||||
|
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue({
|
||||||
|
...mockSecretKey,
|
||||||
|
secret: 'test-secret'
|
||||||
|
})
|
||||||
|
;(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(deleteIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecretKey)).rejects.toThrow(
|
||||||
|
new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(mockDb.integrationSecret.deleteMany).not.toHaveBeenCalledWith()
|
||||||
|
expect(mockDb.socialId.findOne).toHaveBeenCalledWith({
|
||||||
|
_id: mockSocialId,
|
||||||
|
personUuid: mockAccount,
|
||||||
|
verifiedOn: { $gt: 0 }
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should throw error when secret not found', async () => {
|
test('should throw error when secret not found', async () => {
|
||||||
@ -859,6 +1224,7 @@ describe('integration methods', () => {
|
|||||||
extra: { service: 'github' }
|
extra: { service: 'github' }
|
||||||
})
|
})
|
||||||
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue(null)
|
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
|
||||||
|
|
||||||
await expect(deleteIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecretKey)).rejects.toThrow(
|
await expect(deleteIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecretKey)).rejects.toThrow(
|
||||||
new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationSecretNotFound, {}))
|
new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationSecretNotFound, {}))
|
||||||
@ -881,8 +1247,10 @@ describe('integration methods', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('getIntegrationSecret', () => {
|
describe('getIntegrationSecret', () => {
|
||||||
|
const mockAccount = 'test-account'
|
||||||
|
const mockSocialId = 'test-social-id' as PersonId
|
||||||
const mockSecretKey: IntegrationSecretKey = {
|
const mockSecretKey: IntegrationSecretKey = {
|
||||||
socialId: 'test-social-id' as PersonId,
|
socialId: mockSocialId,
|
||||||
kind: 'test-kind',
|
kind: 'test-kind',
|
||||||
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
workspaceUuid: 'test-workspace' as WorkspaceUuid,
|
||||||
key: 'test-key'
|
key: 'test-key'
|
||||||
@ -898,11 +1266,56 @@ describe('integration methods', () => {
|
|||||||
extra: { service: 'github' }
|
extra: { service: 'github' }
|
||||||
})
|
})
|
||||||
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue(mockSecret)
|
;(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)
|
const result = await getIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecretKey)
|
||||||
|
|
||||||
expect(result).toEqual(mockSecret)
|
expect(result).toEqual(mockSecret)
|
||||||
expect(mockDb.integrationSecret.findOne).toHaveBeenCalledWith(mockSecretKey)
|
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 () => {
|
test('should return null when integration secret not found', async () => {
|
||||||
@ -910,6 +1323,7 @@ describe('integration methods', () => {
|
|||||||
extra: { service: 'github' }
|
extra: { service: 'github' }
|
||||||
})
|
})
|
||||||
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue(null)
|
;(mockDb.integrationSecret.findOne as jest.Mock).mockResolvedValue(null)
|
||||||
|
;(mockDb.socialId.findOne as jest.Mock).mockResolvedValue({ _id: mockSocialId })
|
||||||
|
|
||||||
const result = await getIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecretKey)
|
const result = await getIntegrationSecret(mockCtx, mockDb, mockBranding, mockToken, mockSecretKey)
|
||||||
expect(result).toBeNull()
|
expect(result).toBeNull()
|
||||||
|
@ -47,6 +47,8 @@ import type {
|
|||||||
WorkspaceStatus
|
WorkspaceStatus
|
||||||
} from './types'
|
} from './types'
|
||||||
import {
|
import {
|
||||||
|
integrationServices,
|
||||||
|
findExistingIntegration,
|
||||||
cleanEmail,
|
cleanEmail,
|
||||||
getAccount,
|
getAccount,
|
||||||
getEmailSocialId,
|
getEmailSocialId,
|
||||||
@ -551,9 +553,6 @@ export async function updateSocialId (
|
|||||||
await db.socialId.updateOne({ _id: personId }, { displayValue })
|
await db.socialId.updateOne({ _id: personId }, { displayValue })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move to config?
|
|
||||||
const integrationServices = ['github', 'telegram-bot', 'telegram', 'mailbox']
|
|
||||||
|
|
||||||
export async function createIntegration (
|
export async function createIntegration (
|
||||||
ctx: MeasureContext,
|
ctx: MeasureContext,
|
||||||
db: AccountDB,
|
db: AccountDB,
|
||||||
@ -561,31 +560,13 @@ export async function createIntegration (
|
|||||||
token: string,
|
token: string,
|
||||||
params: Integration
|
params: Integration
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { extra } = decodeTokenVerbose(ctx, token)
|
const { extra, account } = decodeTokenVerbose(ctx, token)
|
||||||
verifyAllowedServices(integrationServices, extra)
|
const existing = await findExistingIntegration(account, db, params, extra)
|
||||||
const { socialId, kind, workspaceUuid, data } = params
|
|
||||||
|
|
||||||
if (kind == null || socialId == null || workspaceUuid === undefined) {
|
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingSocialId = await db.socialId.findOne({ _id: socialId })
|
|
||||||
if (existingSocialId == null) {
|
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.SocialIdNotFound, { _id: socialId }))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (workspaceUuid != null) {
|
|
||||||
const workspace = await getWorkspaceById(db, workspaceUuid)
|
|
||||||
if (workspace == null) {
|
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspaceUuid }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const existing = await db.integration.findOne({ socialId, kind, workspaceUuid })
|
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationAlreadyExists, {}))
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationAlreadyExists, {}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { socialId, kind, workspaceUuid, data } = params
|
||||||
await db.integration.insertOne({ socialId, kind, workspaceUuid, data })
|
await db.integration.insertOne({ socialId, kind, workspaceUuid, data })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -596,19 +577,13 @@ export async function updateIntegration (
|
|||||||
token: string,
|
token: string,
|
||||||
params: Integration
|
params: Integration
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { extra } = decodeTokenVerbose(ctx, token)
|
const { extra, account } = decodeTokenVerbose(ctx, token)
|
||||||
verifyAllowedServices(integrationServices, extra)
|
const existing = await findExistingIntegration(account, db, params, extra)
|
||||||
const { socialId, kind, workspaceUuid, data } = params
|
|
||||||
|
|
||||||
if (kind == null || socialId == null || workspaceUuid === undefined) {
|
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const existing = await db.integration.findOne({ socialId, kind, workspaceUuid })
|
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { socialId, kind, workspaceUuid, data } = params
|
||||||
await db.integration.updateOne({ socialId, kind, workspaceUuid }, { data })
|
await db.integration.updateOne({ socialId, kind, workspaceUuid }, { data })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -619,19 +594,13 @@ export async function deleteIntegration (
|
|||||||
token: string,
|
token: string,
|
||||||
params: IntegrationKey
|
params: IntegrationKey
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { extra } = decodeTokenVerbose(ctx, token)
|
const { extra, account } = decodeTokenVerbose(ctx, token)
|
||||||
verifyAllowedServices(integrationServices, extra)
|
const existing = await findExistingIntegration(account, db, params, extra)
|
||||||
const { socialId, kind, workspaceUuid } = params
|
|
||||||
|
|
||||||
if (kind == null || socialId == null || workspaceUuid === undefined) {
|
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const existing = await db.integration.findOne({ socialId, kind, workspaceUuid })
|
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { socialId, kind, workspaceUuid } = params
|
||||||
await db.integrationSecret.deleteMany({ socialId, kind, workspaceUuid })
|
await db.integrationSecret.deleteMany({ socialId, kind, workspaceUuid })
|
||||||
await db.integration.deleteMany({ socialId, kind, workspaceUuid })
|
await db.integration.deleteMany({ socialId, kind, workspaceUuid })
|
||||||
}
|
}
|
||||||
@ -710,22 +679,14 @@ export async function addIntegrationSecret (
|
|||||||
token: string,
|
token: string,
|
||||||
params: IntegrationSecret
|
params: IntegrationSecret
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { extra } = decodeTokenVerbose(ctx, token)
|
const { extra, account } = decodeTokenVerbose(ctx, token)
|
||||||
verifyAllowedServices(integrationServices, extra)
|
const existingIntegration = await findExistingIntegration(account, db, params, extra)
|
||||||
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 integrationKey: IntegrationKey = { socialId, kind, workspaceUuid }
|
|
||||||
const secretKey: IntegrationSecretKey = { ...integrationKey, key }
|
|
||||||
|
|
||||||
const existingIntegration = await db.integration.findOne(integrationKey)
|
|
||||||
if (existingIntegration == null) {
|
if (existingIntegration == null) {
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
|
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)
|
const existingSecret = await db.integrationSecret.findOne(secretKey)
|
||||||
if (existingSecret != null) {
|
if (existingSecret != null) {
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationSecretAlreadyExists, {}))
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationSecretAlreadyExists, {}))
|
||||||
@ -741,15 +702,14 @@ export async function updateIntegrationSecret (
|
|||||||
token: string,
|
token: string,
|
||||||
params: IntegrationSecret
|
params: IntegrationSecret
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { extra } = decodeTokenVerbose(ctx, token)
|
const { extra, account } = decodeTokenVerbose(ctx, token)
|
||||||
verifyAllowedServices(integrationServices, extra)
|
const existingIntegration = await findExistingIntegration(account, db, params, extra)
|
||||||
const { socialId, kind, workspaceUuid, key, secret } = params
|
if (existingIntegration == null) {
|
||||||
const secretKey: IntegrationSecretKey = { socialId, kind, workspaceUuid, key }
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
|
||||||
|
|
||||||
if (kind == null || socialId == null || workspaceUuid === undefined || key == null) {
|
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { socialId, kind, workspaceUuid, key, secret } = params
|
||||||
|
const secretKey: IntegrationSecretKey = { socialId, kind, workspaceUuid, key }
|
||||||
const existingSecret = await db.integrationSecret.findOne(secretKey)
|
const existingSecret = await db.integrationSecret.findOne(secretKey)
|
||||||
if (existingSecret == null) {
|
if (existingSecret == null) {
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationSecretNotFound, {}))
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationSecretNotFound, {}))
|
||||||
@ -765,15 +725,14 @@ export async function deleteIntegrationSecret (
|
|||||||
token: string,
|
token: string,
|
||||||
params: IntegrationSecretKey
|
params: IntegrationSecretKey
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { extra } = decodeTokenVerbose(ctx, token)
|
const { extra, account } = decodeTokenVerbose(ctx, token)
|
||||||
verifyAllowedServices(integrationServices, extra)
|
const existingIntegration = await findExistingIntegration(account, db, params, extra)
|
||||||
const { socialId, kind, workspaceUuid, key } = params
|
if (existingIntegration == null) {
|
||||||
const secretKey: IntegrationSecretKey = { socialId, kind, workspaceUuid, key }
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationNotFound, {}))
|
||||||
|
|
||||||
if (kind == null || socialId == null || workspaceUuid === undefined || key == null) {
|
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { socialId, kind, workspaceUuid, key } = params
|
||||||
|
const secretKey: IntegrationSecretKey = { socialId, kind, workspaceUuid, key }
|
||||||
const existingSecret = await db.integrationSecret.findOne(secretKey)
|
const existingSecret = await db.integrationSecret.findOne(secretKey)
|
||||||
if (existingSecret == null) {
|
if (existingSecret == null) {
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationSecretNotFound, {}))
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.IntegrationSecretNotFound, {}))
|
||||||
@ -789,14 +748,13 @@ export async function getIntegrationSecret (
|
|||||||
token: string,
|
token: string,
|
||||||
params: IntegrationSecretKey
|
params: IntegrationSecretKey
|
||||||
): Promise<IntegrationSecret | null> {
|
): Promise<IntegrationSecret | null> {
|
||||||
const { extra } = decodeTokenVerbose(ctx, token)
|
const { extra, account } = decodeTokenVerbose(ctx, token)
|
||||||
verifyAllowedServices(integrationServices, extra)
|
const existingIntegration = await findExistingIntegration(account, db, params, extra)
|
||||||
const { socialId, kind, workspaceUuid, key } = params
|
if (existingIntegration == null) {
|
||||||
|
return null
|
||||||
if (kind == null || socialId == null || workspaceUuid === undefined || key == null) {
|
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { socialId, kind, workspaceUuid, key } = params
|
||||||
const existing = await db.integrationSecret.findOne({ socialId, kind, workspaceUuid, key })
|
const existing = await db.integrationSecret.findOne({ socialId, kind, workspaceUuid, key })
|
||||||
|
|
||||||
return existing
|
return existing
|
||||||
@ -809,10 +767,20 @@ export async function listIntegrationsSecrets (
|
|||||||
token: string,
|
token: string,
|
||||||
params: Partial<IntegrationSecretKey>
|
params: Partial<IntegrationSecretKey>
|
||||||
): Promise<IntegrationSecret[]> {
|
): Promise<IntegrationSecret[]> {
|
||||||
const { extra } = decodeTokenVerbose(ctx, token)
|
const { extra, account } = decodeTokenVerbose(ctx, token)
|
||||||
verifyAllowedServices(integrationServices, extra)
|
|
||||||
const { socialId, kind, workspaceUuid, key } = params
|
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 })
|
return await db.integrationSecret.find({ socialId, kind, workspaceUuid, key })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +56,8 @@ import {
|
|||||||
WorkspaceLoginInfo,
|
WorkspaceLoginInfo,
|
||||||
WorkspaceStatus,
|
WorkspaceStatus,
|
||||||
AccountEventType,
|
AccountEventType,
|
||||||
Meta
|
Meta,
|
||||||
|
Integration
|
||||||
} from './types'
|
} from './types'
|
||||||
import { Analytics } from '@hcengineering/analytics'
|
import { Analytics } from '@hcengineering/analytics'
|
||||||
import { TokenError, decodeTokenVerbose, generateToken } from '@hcengineering/server-token'
|
import { TokenError, decodeTokenVerbose, generateToken } from '@hcengineering/server-token'
|
||||||
@ -1420,3 +1421,43 @@ export async function setTimezoneIfNotDefined (
|
|||||||
ctx.error('Failed to set account timezone', err)
|
ctx.error('Failed to set account timezone', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move to config?
|
||||||
|
export const integrationServices = ['github', 'telegram-bot', 'telegram', 'mailbox']
|
||||||
|
|
||||||
|
export async function findExistingIntegration (
|
||||||
|
account: AccountUuid,
|
||||||
|
db: AccountDB,
|
||||||
|
params: Integration,
|
||||||
|
extra: any
|
||||||
|
): Promise<Integration | null> {
|
||||||
|
const { socialId, kind, workspaceUuid } = params
|
||||||
|
if (kind == null || socialId == null || workspaceUuid === undefined) {
|
||||||
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verifyAllowedServices(integrationServices, extra, false)) {
|
||||||
|
const existingSocialId = await db.socialId.findOne({ _id: socialId, verifiedOn: { $gt: 0 } })
|
||||||
|
if (existingSocialId == null) {
|
||||||
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.SocialIdNotFound, { _id: socialId }))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (account == null) {
|
||||||
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||||
|
}
|
||||||
|
// Allow accounts to operate on their own integrations
|
||||||
|
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, {}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workspaceUuid != null) {
|
||||||
|
const workspace = await getWorkspaceById(db, workspaceUuid)
|
||||||
|
if (workspace == null) {
|
||||||
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspaceUuid }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await db.integration.findOne({ socialId, kind, workspaceUuid })
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user