diff options
author | Maksim Sukharev <antreesy.web@gmail.com> | 2024-01-15 16:59:21 +0100 |
---|---|---|
committer | Maksim Sukharev <antreesy.web@gmail.com> | 2024-01-24 18:06:31 +0100 |
commit | 007ec090df07b673c3a90e8a35dd2ab89764e1ae (patch) | |
tree | 9015274abf242e2547d33cbf8428dd54914593be | |
parent | 1412fd1154cec1df64d957adf007b10d06ac8504 (diff) |
feat(federation): get remote credentials for message services
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
-rw-r--r-- | src/components/NewMessage/NewMessage.vue | 6 | ||||
-rw-r--r-- | src/composables/useFederationAccess.js | 50 | ||||
-rw-r--r-- | src/store/messagesStore.js | 53 | ||||
-rw-r--r-- | src/store/messagesStore.spec.js | 49 | ||||
-rw-r--r-- | src/stores/reactions.js | 10 |
5 files changed, 130 insertions, 38 deletions
diff --git a/src/components/NewMessage/NewMessage.vue b/src/components/NewMessage/NewMessage.vue index dca50c00e..ce6866956 100644 --- a/src/components/NewMessage/NewMessage.vue +++ b/src/components/NewMessage/NewMessage.vue @@ -215,7 +215,6 @@ import { CONVERSATION, PARTICIPANT, PRIVACY } from '../../constants.js' import { EventBus } from '../../services/EventBus.js' import { shareFile } from '../../services/filesSharingServices.js' import { searchPossibleMentions } from '../../services/mentionsService.js' -import { editMessage } from '../../services/messagesService.js' import { useChatExtrasStore } from '../../stores/chatExtras.js' import { useSettingsStore } from '../../stores/settings.js' import { fetchClipboardContent } from '../../utils/clipboard.js' @@ -676,12 +675,11 @@ export default { async handleEdit() { try { - const response = await editMessage({ + await this.$store.dispatch('editMessage', { token: this.token, messageId: this.messageToEdit.id, - updatedMessage: this.text.trim() + updatedMessage: this.text.trim(), }) - this.$store.dispatch('processMessage', { token: this.token, message: response.data.ocs.data }) this.chatExtrasStore.removeMessageIdToEdit(this.token) } catch { this.$emit('failure') diff --git a/src/composables/useFederationAccess.js b/src/composables/useFederationAccess.js new file mode 100644 index 000000000..eb2a4378f --- /dev/null +++ b/src/composables/useFederationAccess.js @@ -0,0 +1,50 @@ +/** + * @copyright Copyright (c) 2024 Maksim Sukharev <antreesy.web@gmail.com> + * + * @author Maksim Sukharev <antreesy.web@gmail.com> + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * Provide access credentials and params to access a remote server + * @param {object} conversation conversation object + * @param {string} userId current user id + * @return {object} + */ +export function useFederationAccess(conversation, userId) { + // FIXME useStore() is not accessible outside of components in Vue2 + // Fix after using conversation and userId from the Pinia stores + const { remoteServer, remoteToken, remoteAccessToken } = conversation || {} + if (!remoteServer || !remoteToken || !remoteAccessToken) { + return {} + } + const authString = userId + '@' + window.location.host + ':' + remoteAccessToken + + return { + remoteServer, + remoteToken, + headers: { + Authorization: 'Basic ' + window.btoa(authString), + 'X-Nextcloud-Federation': '1', + // FIXME check headers and keep only needed (* is for CORS) + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'HEAD, GET, POST, PUT, PATCH, DELETE', + 'Access-Control-Allow-Headers': 'Origin, Content-Type, X-Auth-Token', + } + } +} diff --git a/src/store/messagesStore.js b/src/store/messagesStore.js index 27e0cc0a5..a93805173 100644 --- a/src/store/messagesStore.js +++ b/src/store/messagesStore.js @@ -26,6 +26,7 @@ import Vue from 'vue' import { showError } from '@nextcloud/dialogs' +import { useFederationAccess } from '../composables/useFederationAccess.js' import { ATTENDEE, CHAT, @@ -35,6 +36,7 @@ import { fetchNoteToSelfConversation } from '../services/conversationsService.js import { EventBus } from '../services/EventBus.js' import { deleteMessage, + editMessage, updateLastReadMessage, fetchMessages, lookForNewMessages, @@ -618,7 +620,8 @@ const actions = { context.commit('markMessageAsDeleting', { token, id, placeholder }) try { - const response = await deleteMessage({ token, id }) + const remoteOptions = useFederationAccess(context.getters.conversation(token), context.getters.getUserId()) + const response = await deleteMessage({ token, id }, remoteOptions) context.dispatch('processMessage', { token, message: response.data.ocs.data }) return response.status } catch (error) { @@ -629,6 +632,26 @@ const actions = { }, /** + * Edit a message text + * + * @param {object} context default store context; + * @param {object} payload payload; + * @param {string} payload.token The conversation token + * @param {string} payload.messageId The message id + * @param {string} payload.updatedMessage The modified text of the message / file share caption + */ + async editMessage(context, { token, messageId, updatedMessage }) { + try { + const remoteOptions = useFederationAccess(context.getters.conversation(token), context.getters.getUserId()) + const response = await editMessage({ token, messageId, updatedMessage }, remoteOptions) + context.dispatch('processMessage', { token, message: response.data.ocs.data }) + } catch (error) { + console.error(error) + throw error + } + }, + + /** * Creates a temporary message ready to be posted, based * on the message to be replied and the current actor * @@ -788,7 +811,7 @@ const actions = { * @param {boolean} data.updateVisually whether to also clear the marker visually in the UI; */ async clearLastReadMessage(context, { token, updateVisually = false }) { - const conversation = context.getters.conversations[token] + const conversation = context.getters.conversation(token) if (!conversation || !conversation.lastMessage) { return } @@ -808,7 +831,7 @@ const actions = { * @param {boolean} data.updateVisually whether to also update the marker visually in the UI; */ async updateLastReadMessage(context, { token, id = 0, updateVisually = false }) { - const conversation = context.getters.conversations[token] + const conversation = context.getters.conversation(token) if (!conversation || conversation.lastReadMessage === id) { return } @@ -823,9 +846,11 @@ const actions = { context.commit('setVisualLastReadMessageId', { token, id }) } - if (context.getters.getUserId()) { + const userId = context.getters.getUserId() + if (userId) { + const remoteOptions = useFederationAccess(conversation, userId) // only update on server side if there's an actual user, not guest - await updateLastReadMessage(token, id) + await updateLastReadMessage(token, id, remoteOptions) } }, @@ -851,12 +876,13 @@ const actions = { // Assign the new cancel function to our data value context.commit('setCancelFetchMessages', cancel) + const remoteOptions = useFederationAccess(context.getters.conversation(token), context.getters.getUserId()) const response = await request({ token, lastKnownMessageId, includeLastKnown, limit: CHAT.FETCH_LIMIT, - }, requestOptions) + }, { ...requestOptions, ...remoteOptions }) let newestKnownMessageId = 0 @@ -942,11 +968,12 @@ const actions = { // Assign the new cancel function to our data value context.commit('setCancelGetMessageContext', cancel) + const remoteOptions = useFederationAccess(context.getters.conversation(token), context.getters.getUserId()) const response = await request({ token, messageId, limit: CHAT.FETCH_LIMIT / 2, - }, requestOptions) + }, { ...requestOptions, ...remoteOptions }) let oldestKnownMessageId = messageId let newestKnownMessageId = messageId @@ -1062,11 +1089,12 @@ const actions = { // Assign the new cancel function to our data value context.commit('setCancelLookForNewMessages', { cancelFunction: cancel, requestId }) + const remoteOptions = useFederationAccess(context.getters.conversation(token), context.getters.getUserId()) const response = await request({ token, lastKnownMessageId, limit: CHAT.FETCH_LIMIT, - }, requestOptions) + }, { ...requestOptions, ...remoteOptions }) context.commit('setCancelLookForNewMessages', { requestId }) if ('x-chat-last-common-read' in response.headers) { @@ -1198,7 +1226,8 @@ const actions = { }, 30000) try { - const response = await request(temporaryMessage, options) + const remoteOptions = useFederationAccess(context.getters.conversation(token), context.getters.getUserId()) + const response = await request(temporaryMessage, { ...options, ...remoteOptions }) clearTimeout(timeout) context.commit('setCancelPostNewMessage', { messageId: temporaryMessage.id, cancelFunction: null }) @@ -1327,6 +1356,7 @@ const actions = { if (message.messageParameters?.object) { const richObject = message.messageParameters.object + const remoteOptions = useFederationAccess(context.getters.conversation(targetToken), context.getters.getUserId()) const response = await postRichObjectToConversation( targetToken, { @@ -1335,6 +1365,7 @@ const actions = { metaData: JSON.stringify(richObject), referenceId: '', }, + remoteOptions, ) return response } @@ -1349,8 +1380,8 @@ const actions = { } } - return await postNewMessage(message, { silent: false }) - + const remoteOptions = useFederationAccess(context.getters.conversation(targetToken), context.getters.getUserId()) + return await postNewMessage(message, { silent: false, ...remoteOptions }) }, async removeExpiredMessages(context, { token }) { diff --git a/src/store/messagesStore.spec.js b/src/store/messagesStore.spec.js index 68819f9a4..35cf280bb 100644 --- a/src/store/messagesStore.spec.js +++ b/src/store/messagesStore.spec.js @@ -244,7 +244,7 @@ describe('messagesStore', () => { const status = await store.dispatch('deleteMessage', { token: message.token, id: message.id, placeholder: 'placeholder-text' }) - expect(deleteMessage).toHaveBeenCalledWith({ token: message.token, id: message.id }) + expect(deleteMessage).toHaveBeenCalledWith({ token: message.token, id: message.id }, {}) expect(status).toBe(200) expect(store.getters.messagesList(TOKEN)).toMatchObject([{ @@ -273,7 +273,7 @@ describe('messagesStore', () => { const status = await store.dispatch('deleteMessage', { token: message.token, id: message.id, placeholder: 'placeholder-text' }) - expect(deleteMessage).toHaveBeenCalledWith({ token: message.token, id: message.id }) + expect(deleteMessage).toHaveBeenCalledWith({ token: message.token, id: message.id }, {}) expect(status).toBe(200) expect(store.getters.messagesList(TOKEN)).toMatchObject([message]) @@ -611,14 +611,13 @@ describe('messagesStore', () => { }) describe('last read message markers', () => { - let conversationsMock + let conversationMock let markConversationReadAction let getUserIdMock let updateConversationLastReadMessageMock beforeEach(() => { - const conversations = {} - conversations[TOKEN] = { + const conversation = { lastMessage: { id: 123, }, @@ -627,10 +626,10 @@ describe('messagesStore', () => { testStoreConfig = cloneDeep(messagesStore) getUserIdMock = jest.fn() - conversationsMock = jest.fn().mockReturnValue(conversations) + conversationMock = jest.fn().mockReturnValue(conversation) markConversationReadAction = jest.fn() updateConversationLastReadMessageMock = jest.fn() - testStoreConfig.getters.conversations = conversationsMock + testStoreConfig.getters.conversation = jest.fn().mockReturnValue(conversationMock) testStoreConfig.getters.getUserId = jest.fn().mockReturnValue(getUserIdMock) testStoreConfig.actions.markConversationRead = markConversationReadAction testStoreConfig.actions.updateConversationLastReadMessage = updateConversationLastReadMessageMock @@ -657,7 +656,7 @@ describe('messagesStore', () => { updateVisually: false, }) - expect(conversationsMock).toHaveBeenCalled() + expect(conversationMock).toHaveBeenCalled() expect(markConversationReadAction).toHaveBeenCalledWith(expect.anything(), TOKEN) expect(getUserIdMock).toHaveBeenCalled() expect(updateConversationLastReadMessageMock).toHaveBeenCalledWith(expect.anything(), { @@ -665,7 +664,7 @@ describe('messagesStore', () => { lastReadMessage: 123, }) - expect(updateLastReadMessage).toHaveBeenCalledWith(TOKEN, 123) + expect(updateLastReadMessage).toHaveBeenCalledWith(TOKEN, 123, {}) expect(store.getters.getVisualLastReadMessageId(TOKEN)).toBe(100) }) @@ -678,7 +677,7 @@ describe('messagesStore', () => { updateVisually: true, }) - expect(conversationsMock).toHaveBeenCalled() + expect(conversationMock).toHaveBeenCalled() expect(markConversationReadAction).toHaveBeenCalledWith(expect.anything(), TOKEN) expect(getUserIdMock).toHaveBeenCalled() expect(updateConversationLastReadMessageMock).toHaveBeenCalledWith(expect.anything(), { @@ -686,7 +685,7 @@ describe('messagesStore', () => { lastReadMessage: 123, }) - expect(updateLastReadMessage).toHaveBeenCalledWith(TOKEN, 123) + expect(updateLastReadMessage).toHaveBeenCalledWith(TOKEN, 123, {}) expect(store.getters.getVisualLastReadMessageId(TOKEN)).toBe(123) }) @@ -699,7 +698,7 @@ describe('messagesStore', () => { updateVisually: true, }) - expect(conversationsMock).toHaveBeenCalled() + expect(conversationMock).toHaveBeenCalled() expect(markConversationReadAction).toHaveBeenCalledWith(expect.anything(), TOKEN) expect(getUserIdMock).toHaveBeenCalled() expect(updateConversationLastReadMessageMock).toHaveBeenCalledWith(expect.anything(), { @@ -721,7 +720,7 @@ describe('messagesStore', () => { updateVisually: false, }) - expect(conversationsMock).toHaveBeenCalled() + expect(conversationMock).toHaveBeenCalled() expect(markConversationReadAction).not.toHaveBeenCalled() expect(getUserIdMock).toHaveBeenCalled() expect(updateConversationLastReadMessageMock).toHaveBeenCalledWith(expect.anything(), { @@ -729,7 +728,7 @@ describe('messagesStore', () => { lastReadMessage: 200, }) - expect(updateLastReadMessage).toHaveBeenCalledWith(TOKEN, 200) + expect(updateLastReadMessage).toHaveBeenCalledWith(TOKEN, 200, {}) expect(store.getters.getVisualLastReadMessageId(TOKEN)).toBe(100) }) @@ -743,7 +742,7 @@ describe('messagesStore', () => { updateVisually: true, }) - expect(conversationsMock).toHaveBeenCalled() + expect(conversationMock).toHaveBeenCalled() expect(markConversationReadAction).not.toHaveBeenCalled() expect(getUserIdMock).toHaveBeenCalled() expect(updateConversationLastReadMessageMock).toHaveBeenCalledWith(expect.anything(), { @@ -751,7 +750,7 @@ describe('messagesStore', () => { lastReadMessage: 200, }) - expect(updateLastReadMessage).toHaveBeenCalledWith(TOKEN, 200) + expect(updateLastReadMessage).toHaveBeenCalledWith(TOKEN, 200, {}) expect(store.getters.getVisualLastReadMessageId(TOKEN)).toBe(200) }) @@ -765,7 +764,7 @@ describe('messagesStore', () => { updateVisually: true, }) - expect(conversationsMock).toHaveBeenCalled() + expect(conversationMock).toHaveBeenCalled() expect(markConversationReadAction).not.toHaveBeenCalled() expect(getUserIdMock).toHaveBeenCalled() expect(updateConversationLastReadMessageMock).toHaveBeenCalledWith(expect.anything(), { @@ -780,6 +779,8 @@ describe('messagesStore', () => { describe('fetchMessages', () => { let updateLastCommonReadMessageAction + let conversationMock + let getUserIdMock let addGuestNameAction let cancelFunctionMock @@ -791,6 +792,10 @@ describe('messagesStore', () => { addGuestNameAction = jest.fn() testStoreConfig.actions.updateLastCommonReadMessage = updateLastCommonReadMessageAction guestNameStore.addGuestName = addGuestNameAction + conversationMock = jest.fn().mockReturnValue({}) + getUserIdMock = jest.fn() + testStoreConfig.getters.conversation = jest.fn().mockReturnValue(conversationMock) + testStoreConfig.getters.getUserId = jest.fn().mockReturnValue(getUserIdMock) cancelFunctionMock = jest.fn() CancelableRequest.mockImplementation((request) => { @@ -939,12 +944,12 @@ describe('messagesStore', () => { let cancelFunctionMock beforeEach(() => { - testStoreConfig = cloneDeep(messagesStore) + testStoreConfig = cloneDeep(storeConfig) const guestNameStore = useGuestNameStore() updateLastCommonReadMessageAction = jest.fn() addGuestNameAction = jest.fn() - testStoreConfig.actions.updateLastCommonReadMessage = updateLastCommonReadMessageAction + testStoreConfig.modules.conversationsStore.actions.updateLastCommonReadMessage = updateLastCommonReadMessageAction guestNameStore.addGuestName = addGuestNameAction cancelFunctionMock = jest.fn() @@ -1062,7 +1067,7 @@ describe('messagesStore', () => { lastKnownMessageId: 3, includeLastKnown: false, limit: CHAT.FETCH_LIMIT, - }, undefined) + }, {}) expect(updateLastCommonReadMessageAction).toHaveBeenCalledTimes(2) expect(updateLastCommonReadMessageAction).toHaveBeenNthCalledWith(1, expect.anything(), { token: TOKEN, lastCommonReadMessage: 2 }) @@ -1528,6 +1533,7 @@ describe('messagesStore', () => { describe('posting new message', () => { let message1 let conversationMock + let userIdMock let updateLastCommonReadMessageAction let updateLastReadMessageAction let updateConversationLastMessageAction @@ -1541,10 +1547,12 @@ describe('messagesStore', () => { restoreConsole = mockConsole(['error']) conversationMock = jest.fn() + userIdMock = jest.fn() updateConversationLastMessageAction = jest.fn() updateLastCommonReadMessageAction = jest.fn() updateLastReadMessageAction = jest.fn() testStoreConfig.getters.conversation = jest.fn().mockReturnValue(conversationMock) + testStoreConfig.getters.getUserId = jest.fn().mockReturnValue(userIdMock) testStoreConfig.actions.updateConversationLastMessage = updateConversationLastMessageAction testStoreConfig.actions.updateLastCommonReadMessage = updateLastCommonReadMessageAction // mock this complex local action as we already tested it elsewhere @@ -1931,6 +1939,7 @@ describe('messagesStore', () => { metaData: JSON.stringify(objectToBeForwarded), referenceId: '', }, + {} ) }) diff --git a/src/stores/reactions.js b/src/stores/reactions.js index 58606b922..4cd16387b 100644 --- a/src/stores/reactions.js +++ b/src/stores/reactions.js @@ -26,6 +26,7 @@ import Vue from 'vue' import { showError } from '@nextcloud/dialogs' +import { useFederationAccess } from '../composables/useFederationAccess.js' import { getReactionsDetails, addReactionToMessage, @@ -238,8 +239,9 @@ export const useReactionsStore = defineStore('reactions', { messageId, reaction: selectedEmoji, }) + const remoteOptions = useFederationAccess(store.getters.conversation(token), store.getters.getUserId()) // The response return an array with the reaction details for this message - const response = await addReactionToMessage(token, messageId, selectedEmoji) + const response = await addReactionToMessage(token, messageId, selectedEmoji, remoteOptions) this.updateReactions({ token, messageId, @@ -272,8 +274,9 @@ export const useReactionsStore = defineStore('reactions', { messageId, reaction: selectedEmoji, }) + const remoteOptions = useFederationAccess(store.getters.conversation(token), store.getters.getUserId()) // The response return an array with the reaction details for this message - const response = await removeReactionFromMessage(token, messageId, selectedEmoji) + const response = await removeReactionFromMessage(token, messageId, selectedEmoji, remoteOptions) this.updateReactions({ token, messageId, @@ -301,7 +304,8 @@ export const useReactionsStore = defineStore('reactions', { async fetchReactions(token, messageId) { console.debug('getting reactions details') try { - const response = await getReactionsDetails(token, messageId) + const remoteOptions = useFederationAccess(store.getters.conversation(token), store.getters.getUserId()) + const response = await getReactionsDetails(token, messageId, remoteOptions) this.updateReactions({ token, messageId, |