summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaksim Sukharev <antreesy.web@gmail.com>2024-01-15 16:59:21 +0100
committerMaksim Sukharev <antreesy.web@gmail.com>2024-01-24 18:06:31 +0100
commit007ec090df07b673c3a90e8a35dd2ab89764e1ae (patch)
tree9015274abf242e2547d33cbf8428dd54914593be
parent1412fd1154cec1df64d957adf007b10d06ac8504 (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.vue6
-rw-r--r--src/composables/useFederationAccess.js50
-rw-r--r--src/store/messagesStore.js53
-rw-r--r--src/store/messagesStore.spec.js49
-rw-r--r--src/stores/reactions.js10
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,