feat: adds verification when inviting external matrixIds (#28096)
parent
653987b85a
commit
19aec23cda
@ -0,0 +1,7 @@ |
||||
--- |
||||
'@rocket.chat/core-services': minor |
||||
'@rocket.chat/rest-typings': minor |
||||
'@rocket.chat/meteor': minor |
||||
--- |
||||
|
||||
New AddUser workflow for Federated Rooms |
||||
@ -0,0 +1,24 @@ |
||||
import { Federation, FederationEE } from '@rocket.chat/core-services'; |
||||
import { isFederationVerifyMatrixIdProps } from '@rocket.chat/rest-typings'; |
||||
|
||||
import { isEnterprise } from '../../../../ee/app/license/server'; |
||||
import { API } from '../api'; |
||||
|
||||
API.v1.addRoute( |
||||
'federation/matrixIds.verify', |
||||
{ |
||||
authRequired: true, |
||||
validateParams: isFederationVerifyMatrixIdProps, |
||||
}, |
||||
{ |
||||
async get() { |
||||
const { matrixIds } = this.queryParams; |
||||
|
||||
const federationService = isEnterprise() ? FederationEE : Federation; |
||||
|
||||
const results = await federationService.verifyMatrixIds(matrixIds); |
||||
|
||||
return API.v1.success({ results: Object.fromEntries(results) }); |
||||
}, |
||||
}, |
||||
); |
||||
@ -0,0 +1,83 @@ |
||||
import { Modal, Button, Box, Icon } from '@rocket.chat/fuselage'; |
||||
import { useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; |
||||
import type { ComponentProps, ReactElement } from 'react'; |
||||
import React from 'react'; |
||||
import { useForm } from 'react-hook-form'; |
||||
|
||||
type AddMatrixUsersModalProps = { |
||||
matrixIdVerifiedStatus: Map<string, string>; |
||||
completeUserList: string[]; |
||||
onClose: () => void; |
||||
onSave: (args_0: any) => Promise<void>; |
||||
}; |
||||
|
||||
type FormValues = { |
||||
usersToInvite: string[]; |
||||
}; |
||||
|
||||
const verificationStatusAsIcon = (verificationStatus: string) => { |
||||
if (verificationStatus === 'VERIFIED') { |
||||
return 'circle-check'; |
||||
} |
||||
|
||||
if (verificationStatus === 'UNVERIFIED') { |
||||
return 'circle-cross'; |
||||
} |
||||
|
||||
if (verificationStatus === 'UNABLE_TO_VERIFY') { |
||||
return 'circle-exclamation'; |
||||
} |
||||
}; |
||||
|
||||
const AddMatrixUsersModal = ({ onClose, matrixIdVerifiedStatus, onSave, completeUserList }: AddMatrixUsersModalProps): ReactElement => { |
||||
const dispatchToastMessage = useToastMessageDispatch(); |
||||
const usersToInvite = completeUserList.filter( |
||||
(user) => !(matrixIdVerifiedStatus.has(user) && matrixIdVerifiedStatus.get(user) === 'UNVERIFIED'), |
||||
); |
||||
|
||||
const { handleSubmit } = useForm<FormValues>({ |
||||
defaultValues: { |
||||
usersToInvite, |
||||
}, |
||||
}); |
||||
|
||||
const onSubmit = (data: FormValues) => { |
||||
onSave({ users: data.usersToInvite }) |
||||
.then(onClose) |
||||
.catch((error) => dispatchToastMessage({ type: 'error', message: error as Error })); |
||||
}; |
||||
|
||||
const t = useTranslation(); |
||||
|
||||
return ( |
||||
<Modal> |
||||
<Modal.Header> |
||||
<Modal.HeaderText> |
||||
<Modal.Title>Sending Invitations</Modal.Title> |
||||
</Modal.HeaderText> |
||||
<Modal.Close title={t('Close')} onClick={onClose} /> |
||||
</Modal.Header> |
||||
<Modal.Content> |
||||
<Box> |
||||
<Box is='ul'> |
||||
{[...matrixIdVerifiedStatus.entries()].map(([_matrixId, _verificationStatus]) => ( |
||||
<li key={_matrixId}> |
||||
{_matrixId}: <Icon name={verificationStatusAsIcon(_verificationStatus) as ComponentProps<typeof Icon>['name']} size='x20' /> |
||||
</li> |
||||
))} |
||||
</Box> |
||||
</Box> |
||||
</Modal.Content> |
||||
<Modal.Footer justifyContent='center'> |
||||
<Modal.FooterControllers> |
||||
<Button onClick={onClose}>{t('Cancel')}</Button> |
||||
<Button primary onClick={handleSubmit(onSubmit)} disabled={!(usersToInvite.length > 0)}> |
||||
{t('Yes_continue')} |
||||
</Button> |
||||
</Modal.FooterControllers> |
||||
</Modal.Footer> |
||||
</Modal> |
||||
); |
||||
}; |
||||
|
||||
export default AddMatrixUsersModal; |
||||
@ -0,0 +1,39 @@ |
||||
import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; |
||||
import { useSetModal, useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts'; |
||||
import { useMutation } from '@tanstack/react-query'; |
||||
import React from 'react'; |
||||
|
||||
import AddMatrixUsersModal from './AddMatrixUsersModal'; |
||||
|
||||
export type useAddMatrixUsersProps = { |
||||
handleSave: (args_0: any) => Promise<void>; |
||||
users: string[]; |
||||
}; |
||||
|
||||
export const useAddMatrixUsers = () => { |
||||
const setModal = useSetModal(); |
||||
const dispatchToastMessage = useToastMessageDispatch(); |
||||
const handleClose = useMutableCallback(() => setModal(null)); |
||||
const dispatchVerifyEndpoint = useEndpoint('GET', '/v1/federation/matrixIds.verify'); |
||||
|
||||
return useMutation(async ({ users, handleSave }: useAddMatrixUsersProps) => { |
||||
try { |
||||
let matrixIdVerificationMap = new Map(); |
||||
const matrixIds = users.filter((user) => user.startsWith('@')); |
||||
const matrixIdsVerificationResponse = await dispatchVerifyEndpoint({ matrixIds }); |
||||
const { results: matrixIdsVerificationResults } = matrixIdsVerificationResponse; |
||||
matrixIdVerificationMap = new Map(Object.entries(matrixIdsVerificationResults)); |
||||
|
||||
setModal( |
||||
<AddMatrixUsersModal |
||||
completeUserList={users} |
||||
onClose={handleClose} |
||||
onSave={handleSave} |
||||
matrixIdVerifiedStatus={matrixIdVerificationMap as Map<string, string>} |
||||
/>, |
||||
); |
||||
} catch (error) { |
||||
dispatchToastMessage({ type: 'error', message: error as Error }); |
||||
} |
||||
}); |
||||
}; |
||||
@ -0,0 +1,58 @@ |
||||
export const enum HttpStatusCodes { |
||||
CONTINUE = 100, |
||||
SWITCHING_PROTOCOLS = 101, |
||||
PROCESSING = 102, |
||||
OK = 200, |
||||
CREATED = 201, |
||||
ACCEPTED = 202, |
||||
NON_AUTHORITATIVE_INFORMATION = 203, |
||||
NO_CONTENT = 204, |
||||
RESET_CONTENT = 205, |
||||
PARTIAL_CONTENT = 206, |
||||
MULTI_STATUS = 207, |
||||
MULTIPLE_CHOICES = 300, |
||||
MOVED_PERMANENTLY = 301, |
||||
MOVED_TEMPORARILY = 302, |
||||
SEE_OTHER = 303, |
||||
NOT_MODIFIED = 304, |
||||
USE_PROXY = 305, |
||||
TEMPORARY_REDIRECT = 307, |
||||
PERMANENT_REDIRECT = 308, |
||||
BAD_REQUEST = 400, |
||||
UNAUTHORIZED = 401, |
||||
PAYMENT_REQUIRED = 402, |
||||
FORBIDDEN = 403, |
||||
NOT_FOUND = 404, |
||||
METHOD_NOT_ALLOWED = 405, |
||||
NOT_ACCEPTABLE = 406, |
||||
PROXY_AUTHENTICATION_REQUIRED = 407, |
||||
REQUEST_TIMEOUT = 408, |
||||
CONFLICT = 409, |
||||
GONE = 410, |
||||
LENGTH_REQUIRED = 411, |
||||
PRECONDITION_FAILED = 412, |
||||
REQUEST_TOO_LONG = 413, |
||||
REQUEST_URI_TOO_LONG = 414, |
||||
UNSUPPORTED_MEDIA_TYPE = 415, |
||||
REQUESTED_RANGE_NOT_SATISFIABLE = 416, |
||||
EXPECTATION_FAILED = 417, |
||||
IM_A_TEAPOT = 418, |
||||
INSUFFICIENT_SPACE_ON_RESOURCE = 419, |
||||
METHOD_FAILURE = 420, |
||||
MISDIRECTED_REQUEST = 421, |
||||
UNPROCESSABLE_ENTITY = 422, |
||||
LOCKED = 423, |
||||
FAILED_DEPENDENCY = 424, |
||||
PRECONDITION_REQUIRED = 428, |
||||
TOO_MANY_REQUESTS = 429, |
||||
REQUEST_HEADER_FIELDS_TOO_LARGE = 431, |
||||
UNAVAILABLE_FOR_LEGAL_REASONS = 451, |
||||
INTERNAL_SERVER_ERROR = 500, |
||||
NOT_IMPLEMENTED = 501, |
||||
BAD_GATEWAY = 502, |
||||
SERVICE_UNAVAILABLE = 503, |
||||
GATEWAY_TIMEOUT = 504, |
||||
HTTP_VERSION_NOT_SUPPORTED = 505, |
||||
INSUFFICIENT_STORAGE = 507, |
||||
NETWORK_AUTHENTICATION_REQUIRED = 511, |
||||
} |
||||
@ -0,0 +1,25 @@ |
||||
export const removeExternalSpecificCharsFromExternalIdentifier = (matrixId = ''): string => { |
||||
return matrixId.replace('@', '').replace('!', '').replace('#', ''); |
||||
}; |
||||
|
||||
export const formatExternalUserIdToInternalUsernameFormat = (matrixId = ''): string => { |
||||
return matrixId.split(':')[0]?.replace('@', ''); |
||||
}; |
||||
|
||||
export const formatExternalAliasIdToInternalFormat = (alias = ''): string => { |
||||
return alias.split(':')[0]?.replace('#', ''); |
||||
}; |
||||
|
||||
export const isAnExternalIdentifierFormat = (identifier: string): boolean => identifier.includes(':'); |
||||
|
||||
export const isAnExternalUserIdFormat = (userId: string): boolean => isAnExternalIdentifierFormat(userId) && userId.includes('@'); |
||||
|
||||
export const extractServerNameFromExternalIdentifier = (identifier = ''): string => { |
||||
const splitted = identifier.split(':'); |
||||
|
||||
return splitted.length > 1 ? splitted[1] : ''; |
||||
}; |
||||
|
||||
export const extractUserIdAndHomeserverFromMatrixId = (matrixId = ''): string[] => { |
||||
return matrixId.replace('@', '').split(':'); |
||||
}; |
||||
@ -0,0 +1,7 @@ |
||||
export const enum VerificationStatus { |
||||
VERIFIED = 'VERIFIED', |
||||
UNVERIFIED = 'UNVERIFIED', |
||||
UNABLE_TO_VERIFY = 'UNABLE_TO_VERIFY', |
||||
} |
||||
|
||||
export const MATRIX_USER_IN_USE = 'M_USER_IN_USE'; |
||||
@ -0,0 +1,22 @@ |
||||
import Ajv from 'ajv'; |
||||
|
||||
const ajv = new Ajv(); |
||||
|
||||
export type FederationVerifyMatrixIdProps = { |
||||
matrixIds: string[]; |
||||
}; |
||||
|
||||
const FederationVerifyMatrixIdPropsSchema = { |
||||
type: 'object', |
||||
properties: { |
||||
matrixIds: { |
||||
type: 'array', |
||||
items: [{ type: 'string' }], |
||||
uniqueItems: true, |
||||
}, |
||||
}, |
||||
additionalProperties: false, |
||||
required: ['matrixIds'], |
||||
}; |
||||
|
||||
export const isFederationVerifyMatrixIdProps = ajv.compile<FederationVerifyMatrixIdProps>(FederationVerifyMatrixIdPropsSchema); |
||||
Loading…
Reference in new issue