diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index f99125f02..8583eb46a 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -37,6 +37,7 @@ import { import { GroupInvitationMessage } from '../session/messages/outgoing/visibleMessage/GroupInvitationMessage'; import { ReadReceiptMessage } from '../session/messages/outgoing/controlMessage/receipt/ReadReceiptMessage'; import { OpenGroup } from '../opengroup/opengroupV1/OpenGroup'; +import { openGroupPrefixRegex } from '../opengroup/utils/OpenGroupUtils'; export enum ConversationType { GROUP = 'group', @@ -188,7 +189,7 @@ export class ConversationModel extends Backbone.Model { return UserUtils.isUsFromCache(this.id); } public isPublic() { - return !!(this.id && this.id.match(OpenGroup.openGroupPrefixRegex)); + return !!(this.id && this.id.match(openGroupPrefixRegex)); } public isOpenGroupV2() { return this.get('type') === ConversationType.OPEN_GROUP; diff --git a/ts/models/message.ts b/ts/models/message.ts index c05a4bf04..2e277d061 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -23,6 +23,8 @@ import { getSuggestedFilenameSending } from '../types/Attachment'; import { actions as conversationActions } from '../state/ducks/conversations'; import { VisibleMessage } from '../session/messages/outgoing/visibleMessage/VisibleMessage'; import { buildSyncMessage } from '../session/utils/syncUtils'; +import { isOpenGroupV2 } from '../opengroup/utils/OpenGroupUtils'; +import { banUser } from '../opengroup/opengroupV2/OpenGroupAPIV2'; export class MessageModel extends Backbone.Model { public propsForTimerNotification: any; public propsForGroupNotification: any; @@ -715,14 +717,17 @@ export class MessageModel extends Backbone.Model { window.log.info('cannot ban user, the corresponding conversation was not found.'); return; } - - const channelAPI = await conversation.getPublicSendData(); - if (!channelAPI) { - window.log.info('cannot ban user, the corresponding channelAPI was not found.'); - return; + let success = false; + if (isOpenGroupV2(conversation.id)) { + await banUser(); + } else { + const channelAPI = await conversation.getPublicSendData(); + if (!channelAPI) { + window.log.info('cannot ban user, the corresponding channelAPI was not found.'); + return; + } + success = await channelAPI.banUser(source); } - const success = await channelAPI.banUser(source); - if (success) { ToastUtils.pushUserBanSuccess(); } else { diff --git a/ts/opengroup/opengroupV1/OpenGroup.ts b/ts/opengroup/opengroupV1/OpenGroup.ts index e596deeb0..36e4cd60a 100644 --- a/ts/opengroup/opengroupV1/OpenGroup.ts +++ b/ts/opengroup/opengroupV1/OpenGroup.ts @@ -3,7 +3,7 @@ import { ConversationController } from '../../session/conversations'; import { PromiseUtils } from '../../session/utils'; import { allowOnlyOneAtATime } from '../../session/utils/Promise'; import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/syncUtils'; -import { prefixify } from '../utils/OpenGroupUtils'; +import { openGroupPrefix, prefixify } from '../utils/OpenGroupUtils'; interface OpenGroupParams { server: string; @@ -12,20 +12,11 @@ interface OpenGroupParams { } export class OpenGroup { - /** - * Just a constant to have less `publicChat:` everywhere - * Note: It does already have the ':' included - */ - public static readonly openGroupPrefix = 'publicChat:'; - /** - * Just a regex to match a public chat (i.e. a string starting with publicChat:) - */ - public static readonly openGroupPrefixRegex = new RegExp(`/^${OpenGroup.openGroupPrefix}/`); private static readonly serverRegex = new RegExp( '^((https?:\\/\\/){0,1})([\\w-]{2,}\\.){1,2}[\\w-]{2,}$' ); private static readonly groupIdRegex = new RegExp( - `^${OpenGroup.openGroupPrefix}:[0-9]*@([\\w-]{2,}.){1,2}[\\w-]{2,}$` + `^${openGroupPrefix}:[0-9]*@([\\w-]{2,}.){1,2}[\\w-]{2,}$` ); public readonly server: string; @@ -39,7 +30,7 @@ export class OpenGroup { * * @param params.server The server URL. `https` will be prepended if `http` or `https` is not explicitly set * @param params.channel The server channel - * @param params.groupId The string corresponding to the server. Eg. `${OpenGroup.openGroupPrefix}1@chat.getsession.org` + * @param params.groupId The string corresponding to the server. Eg. `${openGroupPrefix}1@chat.getsession.org` * @param params.conversationId The conversation ID for the backbone model */ constructor(params: OpenGroupParams) { @@ -76,7 +67,7 @@ export class OpenGroup { * Try to make a new instance of `OpenGroup`. * This does NOT respect `ConversationController` and does not guarentee the conversation's existence. * - * @param groupId The string corresponding to the server. Eg. `${OpenGroup.openGroupPrefix}1@chat.getsession.org` + * @param groupId The string corresponding to the server. Eg. `${openGroupPrefix}1@chat.getsession.org` * @param conversationId The conversation ID for the backbone model * @returns `OpenGroup` if valid otherwise returns `undefined`. */ @@ -167,7 +158,7 @@ export class OpenGroup { } const rawServerURL = server.replace(/^https?:\/\//i, '').replace(/[/\\]+$/i, ''); const channelId = 1; - const conversationId = `${OpenGroup.openGroupPrefix}${channelId}@${rawServerURL}`; + const conversationId = `${openGroupPrefix}${channelId}@${rawServerURL}`; // Quickly peak to make sure we don't already have it return ConversationController.getInstance().get(conversationId); @@ -210,7 +201,7 @@ export class OpenGroup { const prefixRegex = new RegExp('https?:\\/\\/'); const strippedServer = server.replace(prefixRegex, ''); - return `${OpenGroup.openGroupPrefix}${channel}@${strippedServer}`; + return `${openGroupPrefix}${channel}@${strippedServer}`; } /** @@ -252,7 +243,7 @@ export class OpenGroup { const rawServerURL = serverUrl.replace(/^https?:\/\//i, '').replace(/[/\\]+$/i, ''); - const conversationId = `${OpenGroup.openGroupPrefix}${channelId}@${rawServerURL}`; + const conversationId = `${openGroupPrefix}${channelId}@${rawServerURL}`; // Quickly peak to make sure we don't already have it const conversationExists = ConversationController.getInstance().get(conversationId); diff --git a/ts/opengroup/opengroupV2/JoinOpenGroupV2.ts b/ts/opengroup/opengroupV2/JoinOpenGroupV2.ts index 0d800daad..6ab9b0480 100644 --- a/ts/opengroup/opengroupV2/JoinOpenGroupV2.ts +++ b/ts/opengroup/opengroupV2/JoinOpenGroupV2.ts @@ -6,24 +6,14 @@ import { import { ConversationController } from '../../session/conversations'; import { PromiseUtils } from '../../session/utils'; import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/syncUtils'; -import { getOpenGroupV2ConversationId, prefixify } from '../utils/OpenGroupUtils'; +import { + getOpenGroupV2ConversationId, + openGroupV2CompleteURLRegex, + prefixify, + publicKeyParam, +} from '../utils/OpenGroupUtils'; import { attemptConnectionV2OneAtATime } from './OpenGroupManagerV2'; -const protocolRegex = '(https?://)?'; -const hostnameRegex = - '(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])'; -const portRegex = '(:[0-9]+)?'; - -// roomIds allows between 2 and 64 of '0-9' or 'a-z' or '_' chars -const roomIdRegex = '[0-9a-z_]{2,64}'; -const publicKeyRegex = '[0-9a-z]{64}'; -const publicKeyParam = 'public_key='; - -const openGroupV2CompleteURLRegex = new RegExp( - `^${protocolRegex}${hostnameRegex}${portRegex}/${roomIdRegex}\\?${publicKeyParam}${publicKeyRegex}$`, - 'gm' -); - // Inputs that should work: // https://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c // http://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c diff --git a/ts/opengroup/utils/OpenGroupUtils.ts b/ts/opengroup/utils/OpenGroupUtils.ts index 84ec3d733..5641d89f2 100644 --- a/ts/opengroup/utils/OpenGroupUtils.ts +++ b/ts/opengroup/utils/OpenGroupUtils.ts @@ -2,6 +2,45 @@ import { default as insecureNodeFetch } from 'node-fetch'; import { sendViaOnion } from '../../session/onions/onionSend'; import { OpenGroup } from '../opengroupV1/OpenGroup'; +export const protocolRegex = '(https?://)?'; +export const hostnameRegex = + '(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])'; +export const portRegex = '(:[0-9]+)?'; + +// roomIds allows between 2 and 64 of '0-9' or 'a-z' or '_' chars +export const roomIdV2Regex = '[0-9a-z_]{2,64}'; +export const publicKeyRegex = '[0-9a-z]{64}'; +export const publicKeyParam = 'public_key='; +export const openGroupV2ServerUrlRegex = new RegExp(`${protocolRegex}${hostnameRegex}${portRegex}`); + +export const openGroupV2CompleteURLRegex = new RegExp( + `^${openGroupV2ServerUrlRegex}/${roomIdV2Regex}\\?${publicKeyParam}${publicKeyRegex}$`, + 'gm' +); + +/** + * Just a constant to have less `publicChat:` everywhere. + * This is the prefix used to identify our open groups in the conversation database (v1 or v2) + * Note: It does already have the ':' included + */ +export const openGroupPrefix = 'publicChat:'; + +/** + * Just a regex to match a public chat (i.e. a string starting with publicChat:) + */ +export const openGroupPrefixRegex = new RegExp(`/^${openGroupPrefix}/`); + +/** + * An open group v1 conversation id can only have the char '1' as roomId + */ +export const openGroupV1ConversationIdRegex = new RegExp( + `${openGroupPrefix}1@${protocolRegex}${hostnameRegex}` +); + +export const openGroupV2ConversationIdRegex = new RegExp( + `${openGroupPrefix}${roomIdV2Regex}@${protocolRegex}${hostnameRegex}${portRegex}` +); + /** * Tries to establish a connection with the specified open group url. * @@ -94,14 +133,37 @@ export function prefixify(server: string, hasSSL: boolean = true): string { } /** - * No sql access. Just how our open groupv2 url looks like - * @returns `${OpenGroup.openGroupPrefix}${roomId}@${serverUrl}` + * No sql access. Just how our open groupv2 url looks like. + * ServerUrl can have the protocol and port included, or not + * @returns `${openGroupPrefix}${roomId}@${serverUrl}` */ export function getOpenGroupV2ConversationId(serverUrl: string, roomId: string) { - if (roomId.length < 2) { - throw new Error('Invalid roomId: too short'); + if (!roomId.match(roomIdV2Regex)) { + throw new Error('getOpenGroupV2ConversationId: Invalid roomId'); + } + if (!serverUrl.match(openGroupV2ServerUrlRegex)) { + throw new Error('getOpenGroupV2ConversationId: Invalid serverUrl'); } - return `${OpenGroup.openGroupPrefix}${roomId}@${serverUrl}`; + return `${openGroupPrefix}${roomId}@${serverUrl}`; } -export function isOpenGroupV2(conversationId: string) {} +/** + * Check if this conversation id corresponds to an OpenGroupV2 conversation. + * No access to database are made. Only regex matches + * @param conversationId the convo id to evaluate + * @returns true if this conversation id matches the Opengroupv2 conversation id regex + */ +export function isOpenGroupV2(conversationId: string) { + if (!conversationId?.match(openGroupPrefixRegex)) { + // this is not even an open group + return false; + } + + if (!conversationId?.match(openGroupV1ConversationIdRegex)) { + // this is an open group v1 + console.warn('this is an open group v1:', conversationId); + return false; + } + + return conversationId.match(openGroupV2ConversationIdRegex); +} diff --git a/ts/receiver/receiver.ts b/ts/receiver/receiver.ts index 4861acae5..c5265f231 100644 --- a/ts/receiver/receiver.ts +++ b/ts/receiver/receiver.ts @@ -23,6 +23,7 @@ import { ConversationController } from '../session/conversations'; import { removeUnprocessed } from '../data/data'; import { ConversationType } from '../models/conversation'; import { OpenGroup } from '../opengroup/opengroupV1/OpenGroup'; +import { openGroupPrefixRegex } from '../opengroup/utils/OpenGroupUtils'; // TODO: check if some of these exports no longer needed @@ -273,8 +274,7 @@ export async function handlePublicMessage(messageData: any) { await updateProfile(conversation, profile, profileKey); } - const isPublicVisibleMessage = - group && group.id && !!group.id.match(OpenGroup.openGroupPrefixRegex); + const isPublicVisibleMessage = group && group.id && !!group.id.match(openGroupPrefixRegex); if (!isPublicVisibleMessage) { throw new Error('handlePublicMessage Should only be called with public message groups'); diff --git a/ts/test/session/unit/utils/Messages_test.ts b/ts/test/session/unit/utils/Messages_test.ts index 71e05f589..068a42f48 100644 --- a/ts/test/session/unit/utils/Messages_test.ts +++ b/ts/test/session/unit/utils/Messages_test.ts @@ -21,6 +21,7 @@ import { ClosedGroupNameChangeMessage } from '../../../../session/messages/outgo import { ClosedGroupNewMessage } from '../../../../session/messages/outgoing/controlMessage/group/ClosedGroupNewMessage'; import { ClosedGroupRemovedMembersMessage } from '../../../../session/messages/outgoing/controlMessage/group/ClosedGroupRemovedMembersMessage'; import { OpenGroup } from '../../../../opengroup/opengroupV1/OpenGroup'; +import { openGroupPrefix } from '../../../../opengroup/utils/OpenGroupUtils'; const { expect } = chai; @@ -227,12 +228,12 @@ describe('Message Utils', () => { let convos: Array; const mockValidOpenGroup = new MockConversation({ type: ConversationType.OPEN_GROUP, - id: `${OpenGroup.openGroupPrefix}1@chat-dev.lokinet.org`, + id: `${openGroupPrefix}1@chat-dev.lokinet.org`, }); const mockValidOpenGroup2 = new MockConversation({ type: ConversationType.OPEN_GROUP, - id: `${OpenGroup.openGroupPrefix}1@chat-dev2.lokinet.org`, + id: `${openGroupPrefix}1@chat-dev2.lokinet.org`, }); const mockValidClosedGroup = new MockConversation({ diff --git a/ts/test/test-utils/utils/message.ts b/ts/test/test-utils/utils/message.ts index 77eec9c80..afe7dfe34 100644 --- a/ts/test/test-utils/utils/message.ts +++ b/ts/test/test-utils/utils/message.ts @@ -5,6 +5,7 @@ import { ConversationAttributes, ConversationType } from '../../../models/conver import { OpenGroupMessage } from '../../../session/messages/outgoing'; import { VisibleMessage } from '../../../session/messages/outgoing/visibleMessage/VisibleMessage'; import { OpenGroup } from '../../../opengroup/opengroupV1/OpenGroup'; +import { openGroupPrefixRegex } from '../../../opengroup/utils/OpenGroupUtils'; export function generateVisibleMessage(identifier?: string): VisibleMessage { return new VisibleMessage({ @@ -92,7 +93,7 @@ export class MockConversation { } public isPublic() { - return this.id.match(OpenGroup.openGroupPrefixRegex); + return this.id.match(openGroupPrefixRegex); } public isMediumGroup() {