mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-11 01:40:32 +00:00
uberf-10228: expose release social id to services (#8562)
This commit is contained in:
parent
498551f4e3
commit
163295783b
@ -152,6 +152,7 @@ export interface AccountClient {
|
||||
) => Promise<PersonId>
|
||||
updateSocialId: (personId: PersonId, displayValue: string) => Promise<PersonId>
|
||||
exchangeGuestToken: (token: string) => Promise<string>
|
||||
releaseSocialId: (personUuid: PersonUuid, type: SocialIdType, value: string) => Promise<void>
|
||||
createIntegration: (integration: Integration) => Promise<void>
|
||||
updateIntegration: (integration: Integration) => Promise<void>
|
||||
deleteIntegration: (integrationKey: IntegrationKey) => Promise<void>
|
||||
@ -781,6 +782,15 @@ class AccountClientImpl implements AccountClient {
|
||||
await this.rpc(request)
|
||||
}
|
||||
|
||||
async releaseSocialId (personUuid: PersonUuid, type: SocialIdType, value: string): Promise<void> {
|
||||
const request = {
|
||||
method: 'releaseSocialId' as const,
|
||||
params: { personUuid, type, value }
|
||||
}
|
||||
|
||||
await this.rpc(request)
|
||||
}
|
||||
|
||||
async createIntegration (integration: Integration): Promise<void> {
|
||||
const request = {
|
||||
method: 'createIntegration' as const,
|
||||
|
@ -29,6 +29,7 @@ import {
|
||||
getIntegrationSecret,
|
||||
listIntegrations,
|
||||
listIntegrationsSecrets,
|
||||
releaseSocialId,
|
||||
updateIntegration,
|
||||
updateIntegrationSecret
|
||||
} from '../serviceOperations'
|
||||
@ -1435,4 +1436,80 @@ describe('integration methods', () => {
|
||||
expect(mockDb.integrationSecret.find).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('releaseSocialId', () => {
|
||||
const mockCtx = {
|
||||
error: jest.fn()
|
||||
} as unknown as MeasureContext
|
||||
|
||||
const mockDb = {} as unknown as AccountDB
|
||||
const mockBranding = null
|
||||
const mockToken = 'test-token'
|
||||
|
||||
// Create spy on doReleaseSocialId
|
||||
const doReleaseSocialIdSpy = jest.spyOn(utils, 'doReleaseSocialId').mockImplementation(async () => {})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
doReleaseSocialIdSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('should allow github service to release social id', async () => {
|
||||
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||
extra: { service: 'github' }
|
||||
})
|
||||
|
||||
const params = {
|
||||
personUuid: 'test-person' as PersonUuid,
|
||||
type: SocialIdType.GITHUB,
|
||||
value: 'test-value'
|
||||
}
|
||||
|
||||
await releaseSocialId(mockCtx, mockDb, mockBranding, mockToken, params)
|
||||
|
||||
expect(doReleaseSocialIdSpy).toHaveBeenCalledWith(mockDb, params.personUuid, params.type, params.value, 'github')
|
||||
})
|
||||
|
||||
test('should throw error for unauthorized service', async () => {
|
||||
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||
extra: { service: 'unauthorized' }
|
||||
})
|
||||
|
||||
const params = {
|
||||
personUuid: 'test-person' as PersonUuid,
|
||||
type: SocialIdType.GITHUB,
|
||||
value: 'test-value'
|
||||
}
|
||||
|
||||
await expect(releaseSocialId(mockCtx, mockDb, mockBranding, mockToken, params)).rejects.toThrow(
|
||||
new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
)
|
||||
|
||||
expect(doReleaseSocialIdSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('should throw error for invalid parameters', async () => {
|
||||
;(decodeTokenVerbose as jest.Mock).mockReturnValue({
|
||||
extra: { service: 'github' }
|
||||
})
|
||||
|
||||
const invalidParams = [
|
||||
{ type: SocialIdType.GITHUB, value: 'test' }, // missing personUuid
|
||||
{ personUuid: 'test' as PersonUuid, value: 'test' }, // missing type
|
||||
{ personUuid: 'test' as PersonUuid, type: SocialIdType.GITHUB }, // missing value
|
||||
{ personUuid: 'test' as PersonUuid, type: 'invalid' as SocialIdType, value: 'test' } // invalid type
|
||||
]
|
||||
|
||||
for (const params of invalidParams) {
|
||||
await expect(releaseSocialId(mockCtx, mockDb, mockBranding, mockToken, params as any)).rejects.toThrow(
|
||||
new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
|
||||
)
|
||||
}
|
||||
|
||||
expect(doReleaseSocialIdSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -64,7 +64,8 @@ import {
|
||||
getWorkspaceInvite,
|
||||
loginOrSignUpWithProvider,
|
||||
sendEmail,
|
||||
addSocialId
|
||||
addSocialId,
|
||||
doReleaseSocialId
|
||||
} from '../utils'
|
||||
// eslint-disable-next-line import/no-named-default
|
||||
import platform, { getMetadata, PlatformError, Severity, Status } from '@hcengineering/platform'
|
||||
@ -2074,4 +2075,90 @@ describe('account utils', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('social id release utils', () => {
|
||||
describe('doReleaseSocialId', () => {
|
||||
const mockDb = {
|
||||
socialId: {
|
||||
find: jest.fn(),
|
||||
updateOne: jest.fn()
|
||||
},
|
||||
account: {
|
||||
findOne: jest.fn()
|
||||
},
|
||||
accountEvent: {
|
||||
insertOne: jest.fn()
|
||||
}
|
||||
} as unknown as AccountDB
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
test('should release social id and create event', async () => {
|
||||
const personUuid = 'test-person' as PersonUuid
|
||||
const type = SocialIdType.GITHUB
|
||||
const value = 'test-value'
|
||||
const releasedBy = 'github'
|
||||
const socialIdId = 'test-social-id' as PersonId
|
||||
|
||||
const mockSocialIds = [
|
||||
{
|
||||
_id: socialIdId,
|
||||
value
|
||||
}
|
||||
]
|
||||
const mockAccount = { uuid: personUuid }
|
||||
|
||||
;(mockDb.socialId.find as jest.Mock).mockResolvedValue(mockSocialIds)
|
||||
;(mockDb.account.findOne as jest.Mock).mockResolvedValue(mockAccount)
|
||||
|
||||
await doReleaseSocialId(mockDb, personUuid, type, value, releasedBy)
|
||||
|
||||
expect(mockDb.socialId.updateOne).toHaveBeenCalledWith(
|
||||
{ _id: socialIdId },
|
||||
{ value: `${value}#${socialIdId}`, isDeleted: true }
|
||||
)
|
||||
expect(mockDb.accountEvent.insertOne).toHaveBeenCalledWith({
|
||||
accountUuid: personUuid,
|
||||
eventType: AccountEventType.SOCIAL_ID_RELEASED,
|
||||
time: expect.any(Number),
|
||||
data: {
|
||||
socialId: socialIdId,
|
||||
releasedBy
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('should handle missing account', async () => {
|
||||
const personUuid = 'test-person' as PersonUuid
|
||||
const type = SocialIdType.GITHUB
|
||||
const value = 'test-value'
|
||||
const socialIds = [{ _id: 'id1' as PersonId, value }]
|
||||
|
||||
;(mockDb.socialId.find as jest.Mock).mockResolvedValue(socialIds)
|
||||
;(mockDb.account.findOne as jest.Mock).mockResolvedValue(null)
|
||||
|
||||
await doReleaseSocialId(mockDb, personUuid, type, value, '')
|
||||
|
||||
expect(mockDb.socialId.updateOne).toHaveBeenCalled()
|
||||
expect(mockDb.accountEvent.insertOne).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('should throw error when no social id found', async () => {
|
||||
const personUuid = 'test-person' as PersonUuid
|
||||
const type = SocialIdType.GITHUB
|
||||
const value = 'test-value'
|
||||
|
||||
;(mockDb.socialId.find as jest.Mock).mockResolvedValue([])
|
||||
|
||||
await expect(doReleaseSocialId(mockDb, personUuid, type, value, '')).rejects.toThrow(
|
||||
new PlatformError(new Status(Severity.ERROR, platform.status.SocialIdNotFound, {}))
|
||||
)
|
||||
|
||||
expect(mockDb.socialId.updateOne).not.toHaveBeenCalled()
|
||||
expect(mockDb.accountEvent.insertOne).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -77,7 +77,7 @@ import {
|
||||
isEmail,
|
||||
isOtpValid,
|
||||
normalizeValue,
|
||||
releaseSocialId,
|
||||
doReleaseSocialId,
|
||||
selectWorkspace,
|
||||
sendEmail,
|
||||
sendEmailConfirmation,
|
||||
@ -359,7 +359,7 @@ export async function createWorkspace (
|
||||
ctx.info('Creating workspace record', { workspaceName, account, region })
|
||||
|
||||
// Any confirmed social ID will do
|
||||
const socialId = await db.socialId.findOne({ personUuid: account, verifiedOn: { $gt: 0 } })
|
||||
const socialId = (await getSocialIds(ctx, db, branding, token, { confirmed: true }))[0]
|
||||
|
||||
if (socialId == null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotConfirmed, {}))
|
||||
@ -1280,7 +1280,7 @@ export async function getLoginInfoByToken (
|
||||
|
||||
if (!isDocGuest && !isSystem) {
|
||||
// Any confirmed social ID will do
|
||||
socialId = await db.socialId.findOne({ personUuid: accountUuid, verifiedOn: { $gt: 0 } })
|
||||
socialId = (await getSocialIds(ctx, db, branding, token, { confirmed: true }))[0]
|
||||
if (socialId == null) {
|
||||
return {
|
||||
account: accountUuid
|
||||
@ -1367,7 +1367,9 @@ export async function getSocialIds (
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
|
||||
return await db.socialId.find({ personUuid: account, verifiedOn: { $gt: 0 } })
|
||||
const socialIds = await db.socialId.find({ personUuid: account, verifiedOn: { $gt: 0 } })
|
||||
|
||||
return socialIds.filter((si) => si.isDeleted !== true)
|
||||
}
|
||||
|
||||
export async function getPerson (
|
||||
@ -1636,7 +1638,7 @@ async function deleteMailbox (
|
||||
|
||||
await db.mailboxSecret.deleteMany({ mailbox })
|
||||
await db.mailbox.deleteMany({ mailbox })
|
||||
await releaseSocialId(db, account, SocialIdType.EMAIL, mailbox)
|
||||
await doReleaseSocialId(db, account, SocialIdType.EMAIL, mailbox, 'deleteMailbox')
|
||||
ctx.info('Mailbox deleted', { mailbox, account })
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,8 @@ import {
|
||||
addSocialId,
|
||||
getWorkspaces,
|
||||
updateWorkspaceRole,
|
||||
getPersonName
|
||||
getPersonName,
|
||||
doReleaseSocialId
|
||||
} from './utils'
|
||||
|
||||
// Note: it is IMPORTANT to always destructure params passed here to avoid sending extra params
|
||||
@ -518,6 +519,26 @@ export async function getPersonInfo (
|
||||
}
|
||||
}
|
||||
|
||||
export async function releaseSocialId (
|
||||
ctx: MeasureContext,
|
||||
db: AccountDB,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
params: { personUuid: PersonUuid, type: SocialIdType, value: string }
|
||||
): Promise<void> {
|
||||
const { extra } = decodeTokenVerbose(ctx, token)
|
||||
|
||||
verifyAllowedServices(['github'], extra)
|
||||
|
||||
const { personUuid, type, value } = params
|
||||
|
||||
if (personUuid == null || !Object.values(SocialIdType).includes(type) || value == null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
|
||||
}
|
||||
|
||||
await doReleaseSocialId(db, personUuid, type, value, extra?.service ?? '')
|
||||
}
|
||||
|
||||
export async function addSocialIdToPerson (
|
||||
ctx: MeasureContext,
|
||||
db: AccountDB,
|
||||
@ -811,6 +832,7 @@ export type AccountServiceMethods =
|
||||
| 'addSocialIdToPerson'
|
||||
| 'updateSocialId'
|
||||
| 'getPersonInfo'
|
||||
| 'releaseSocialId'
|
||||
| 'createIntegration'
|
||||
| 'updateIntegration'
|
||||
| 'deleteIntegration'
|
||||
@ -839,6 +861,7 @@ export function getServiceMethods (): Partial<Record<AccountServiceMethods, Acco
|
||||
addSocialIdToPerson: wrap(addSocialIdToPerson),
|
||||
updateSocialId: wrap(updateSocialId),
|
||||
getPersonInfo: wrap(getPersonInfo),
|
||||
releaseSocialId: wrap(releaseSocialId),
|
||||
createIntegration: wrap(createIntegration),
|
||||
updateIntegration: wrap(updateIntegration),
|
||||
deleteIntegration: wrap(deleteIntegration),
|
||||
|
@ -68,7 +68,8 @@ export interface AccountEvent {
|
||||
}
|
||||
|
||||
export enum AccountEventType {
|
||||
ACCOUNT_CREATED = 'account_created'
|
||||
ACCOUNT_CREATED = 'account_created',
|
||||
SOCIAL_ID_RELEASED = 'social_id_released'
|
||||
}
|
||||
|
||||
export interface Member {
|
||||
|
@ -1370,15 +1370,34 @@ export async function addSocialId (
|
||||
return await db.socialId.insertOne(newSocialId)
|
||||
}
|
||||
|
||||
export async function releaseSocialId (
|
||||
export async function doReleaseSocialId (
|
||||
db: AccountDB,
|
||||
personUuid: PersonUuid,
|
||||
type: SocialIdType,
|
||||
value: string
|
||||
value: string,
|
||||
releasedBy: string
|
||||
): Promise<void> {
|
||||
const socialIds = await db.socialId.find({ personUuid, type, value })
|
||||
|
||||
if (socialIds.length === 0) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.SocialIdNotFound, {}))
|
||||
}
|
||||
|
||||
const account = await db.account.findOne({ uuid: personUuid as AccountUuid })
|
||||
|
||||
for (const socialId of socialIds) {
|
||||
await db.socialId.updateOne({ _id: socialId._id }, { value: `${socialId.value}#${socialId._id}`, isDeleted: true })
|
||||
if (account != null) {
|
||||
await db.accountEvent.insertOne({
|
||||
accountUuid: account.uuid,
|
||||
eventType: AccountEventType.SOCIAL_ID_RELEASED,
|
||||
time: Date.now(),
|
||||
data: {
|
||||
socialId: socialId._id,
|
||||
releasedBy: releasedBy ?? ''
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user