From 13ad752466d69ad2c5ff7f3c0cd629f5af43ba27 Mon Sep 17 00:00:00 2001 From: Aaron Ogle Date: Wed, 20 Mar 2019 15:31:28 -0500 Subject: [PATCH 1/5] Improve cloud section --- app/cloud/client/admin/cloud.html | 11 ++-- app/cloud/client/admin/cloud.js | 24 +++++++ .../server/functions/connectWorkspace.js | 6 +- .../server/functions/disconnectWorkspace.js | 13 ++++ .../functions/getWorkspaceAccessToken.js | 12 +++- .../functions/retrieveRegistrationStatus.js | 6 +- .../functions/startRegisterWorkspace.js | 64 +++++++++++++++++++ .../server/functions/unregisterWorkspace.js | 22 +++++++ app/cloud/server/methods.js | 35 +++++++++- app/setup-wizard/client/setupWizard.js | 14 +++- app/statistics/server/functions/get.js | 1 + 11 files changed, 194 insertions(+), 14 deletions(-) create mode 100644 app/cloud/server/functions/disconnectWorkspace.js create mode 100644 app/cloud/server/functions/startRegisterWorkspace.js create mode 100644 app/cloud/server/functions/unregisterWorkspace.js diff --git a/app/cloud/client/admin/cloud.html b/app/cloud/client/admin/cloud.html index c09e1281a77e..3fb5cd1a9178 100644 --- a/app/cloud/client/admin/cloud.html +++ b/app/cloud/client/admin/cloud.html @@ -16,9 +16,10 @@
- {{#if info.registeredWithWizard}} - {{#if info.workspaceConnected}} + {{#if info.connectToCloud}} + {{#if info.workspaceRegistered}}
+ {{#if info.userAssociated}}

{{_ "Cloud_workspace_connected_plus_account"}}

{{else}} @@ -47,8 +48,6 @@
-
{{ registeredWithWizard }}
-
@@ -62,6 +61,8 @@
+ +

If you still haven't received a registration email please make sure your email is updated above. If you still have issues you can contact support at: support@rocket.chat

{{/if}} {{else}} @@ -72,7 +73,7 @@

{{_ "Cloud_registration_required_description"}}

-

{{_ "Cloud_registration_requried_link_text"}}

+
{{/if}} diff --git a/app/cloud/client/admin/cloud.js b/app/cloud/client/admin/cloud.js index ea835f677207..e0d669323b55 100644 --- a/app/cloud/client/admin/cloud.js +++ b/app/cloud/client/admin/cloud.js @@ -45,6 +45,26 @@ Template.cloud.onCreated(function() { }); }; + instance.registerWorkspace = function _registerWorkspace() { + Meteor.call('cloud:registerWorkspace', (error, success) => { + if (error) { + toastr.error(error); + instance.loadRegStatus(); + return; + } + + if (!success) { + toastr.error('An error occured'); + instance.loadRegStatus(); + return; + } + + toastr.success(t('Connected')); + + instance.loadRegStatus(); + }); + }; + const params = queryString.parse(location.search); if (params.token) { @@ -90,4 +110,8 @@ Template.cloud.events({ i.connectWorkspace(token); }, + + 'click .register-btn'(e, i) { + i.registerWorkspace(); + }, }); diff --git a/app/cloud/server/functions/connectWorkspace.js b/app/cloud/server/functions/connectWorkspace.js index 84b2d0f68d3c..6d2e00cbeb88 100644 --- a/app/cloud/server/functions/connectWorkspace.js +++ b/app/cloud/server/functions/connectWorkspace.js @@ -8,9 +8,9 @@ import { getRedirectUri } from './getRedirectUri'; import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; export function connectWorkspace(token) { - const { registeredWithWizard } = retrieveRegistrationStatus(); - if (!registeredWithWizard) { - return false; + const { connectToCloud } = retrieveRegistrationStatus(); + if (!connectToCloud) { + Settings.updateValueById('Register_Server', true); } const redirectUri = getRedirectUri(); diff --git a/app/cloud/server/functions/disconnectWorkspace.js b/app/cloud/server/functions/disconnectWorkspace.js new file mode 100644 index 000000000000..c1e2adda876e --- /dev/null +++ b/app/cloud/server/functions/disconnectWorkspace.js @@ -0,0 +1,13 @@ +import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; +import { Settings } from '../../../models'; + +export function disconnectWorkspace() { + const { connectToCloud } = retrieveRegistrationStatus(); + if (!connectToCloud) { + return true; + } + + Settings.updateValueById('Register_Server', false); + + return true; +} diff --git a/app/cloud/server/functions/getWorkspaceAccessToken.js b/app/cloud/server/functions/getWorkspaceAccessToken.js index 10e8af35f72b..aea0be8528e3 100644 --- a/app/cloud/server/functions/getWorkspaceAccessToken.js +++ b/app/cloud/server/functions/getWorkspaceAccessToken.js @@ -5,9 +5,13 @@ import { settings } from '../../../settings'; import { Settings } from '../../../models'; import { getRedirectUri } from './getRedirectUri'; +import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; +import { unregisterWorkspace } from './unregisterWorkspace'; export function getWorkspaceAccessToken() { - if (!settings.get('Register_Server')) { + const { connectToCloud, workspaceRegistered } = retrieveRegistrationStatus(); + + if (!connectToCloud || !workspaceRegistered) { return ''; } @@ -39,6 +43,11 @@ export function getWorkspaceAccessToken() { }), }); } catch (e) { + if (e.response && e.response.data && e.response.data.errorCode === 'oauth_invalid_client_credentials') { + console.error('Server has been unregistered from cloud'); + unregisterWorkspace(); + } + return ''; } @@ -48,6 +57,5 @@ export function getWorkspaceAccessToken() { Settings.updateValueById('Cloud_Workspace_Access_Token', authTokenResult.data.access_token); Settings.updateValueById('Cloud_Workspace_Access_Token_Expires_At', expiresAt); - return authTokenResult.data.access_token; } diff --git a/app/cloud/server/functions/retrieveRegistrationStatus.js b/app/cloud/server/functions/retrieveRegistrationStatus.js index 2cc250927f62..c0c1af3636ae 100644 --- a/app/cloud/server/functions/retrieveRegistrationStatus.js +++ b/app/cloud/server/functions/retrieveRegistrationStatus.js @@ -3,9 +3,11 @@ import { Users } from '../../../models'; export function retrieveRegistrationStatus() { const info = { - registeredWithWizard: settings.get('Register_Server'), - workspaceConnected: (settings.get('Cloud_Workspace_Client_Id')) ? true : false, + connectToCloud: settings.get('Register_Server'), + workspaceRegistered: (settings.get('Cloud_Workspace_Client_Id')) ? true : false, userAssociated: (settings.get('Cloud_Workspace_Account_Associated')) ? true : false, + workspaceId: settings.get('Cloud_Workspace_Id'), + uniqueId: settings.get('uniqueID'), token: '', email: '', }; diff --git a/app/cloud/server/functions/startRegisterWorkspace.js b/app/cloud/server/functions/startRegisterWorkspace.js new file mode 100644 index 000000000000..9521fe6aac80 --- /dev/null +++ b/app/cloud/server/functions/startRegisterWorkspace.js @@ -0,0 +1,64 @@ +import { HTTP } from 'meteor/http'; +import { settings } from '../../../settings'; +import { Settings } from '../../../models'; + +import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; + +import { statistics } from '../../../statistics'; + +export function startRegisterWorkspace() { + const { workspaceConnected } = retrieveRegistrationStatus(); + if (workspaceConnected || process.env.TEST_MODE) { + return true; + } + + Settings.updateValueById('Register_Server', true); + + const stats = statistics.get(); + + const address = settings.get('Site_Url'); + + // If we have it lets send it because likely an update + const workspaceId = settings.get('Cloud_Workspace_Id'); + + const regInfo = { + uniqueId: stats.uniqueId, + workspaceId, + address, + contactName: stats.wizard.contactName, + contactEmail: stats.wizard.contactEmail, + accountName: stats.wizard.organizationName, + siteName: stats.wizard.siteName, + deploymentMethod: stats.deploy.method, + deploymentPlatform: stats.deploy.platform, + version: stats.version, + }; + + console.log(regInfo); + + const cloudUrl = settings.get('Cloud_Url'); + + let result; + try { + result = HTTP.post(`${ cloudUrl }/api/v2/register/workspace`, { + data: regInfo, + }); + } catch (e) { + if (e.response && e.response.data && e.response.data.errorCode) { + console.error(`Failed to register with Rocket.Chat Cloud. ErrorCode: ${ e.response.data.errorCode }`); + } + return false; + } + + const { data } = result; + + if (!data) { + return false; + } + + Settings.updateValueById('Cloud_Workspace_Id', data.id); + + console.log(data); + + return true; +} diff --git a/app/cloud/server/functions/unregisterWorkspace.js b/app/cloud/server/functions/unregisterWorkspace.js new file mode 100644 index 000000000000..93fb76fe61f5 --- /dev/null +++ b/app/cloud/server/functions/unregisterWorkspace.js @@ -0,0 +1,22 @@ +import { Settings } from '../../../models'; + +import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; + +export function unregisterWorkspace() { + const { workspaceRegistered } = retrieveRegistrationStatus(); + if (!workspaceRegistered) { + return true; + } + + Settings.removeById('Cloud_Workspace_Id'); + Settings.removeById('Cloud_Workspace_Name'); + Settings.removeById('Cloud_Workspace_Client_Id'); + Settings.removeById('Cloud_Workspace_Client_Secret'); + Settings.removeById('Cloud_Workspace_Client_Secret_Expires_At'); + Settings.removeById('Cloud_Workspace_Registration_Client_Uri'); + + // So doesn't try to register again automatically + Settings.updateValueById('Register_Server', false); + + return true; +} diff --git a/app/cloud/server/methods.js b/app/cloud/server/methods.js index 843b908af07b..402c321fa3bd 100644 --- a/app/cloud/server/methods.js +++ b/app/cloud/server/methods.js @@ -7,6 +7,9 @@ import { retrieveRegistrationStatus } from './functions/retrieveRegistrationStat import { connectWorkspace } from './functions/connectWorkspace'; import { getOAuthAuthorizationUrl } from './functions/getOAuthAuthorizationUrl'; import { finishOAuthAuthorization } from './functions/finishOAuthAuthorization'; +import { startRegisterWorkspace } from './functions/startRegisterWorkspace'; +import { disconnectWorkspace } from './functions/disconnectWorkspace'; +import { settings } from '../../settings/server'; Meteor.methods({ 'cloud:checkRegisterStatus'() { @@ -20,6 +23,17 @@ Meteor.methods({ return retrieveRegistrationStatus(); }, + 'cloud:registerWorkspace'() { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:startRegister' }); + } + + if (!hasPermission(Meteor.userId(), 'manage-cloud')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:startRegister' }); + } + + return startRegisterWorkspace(); + }, 'cloud:updateEmail'(email) { check(email, String); @@ -31,7 +45,15 @@ Meteor.methods({ throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:updateEmail' }); } - Settings.updateValueById('Organization_Email', email); + const existingEmail = settings.get('Organization_Email'); + + if (email !== existingEmail) { + Settings.updateValueById('Organization_Email', email); + + return startRegisterWorkspace(); + } + + return; }, 'cloud:connectWorkspace'(token) { check(token, String); @@ -46,6 +68,17 @@ Meteor.methods({ return connectWorkspace(token); }, + 'cloud:disconnectWorkspace'() { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:connectServer' }); + } + + if (!hasPermission(Meteor.userId(), 'manage-cloud')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:connectServer' }); + } + + return disconnectWorkspace(); + }, 'cloud:getOAuthAuthorizationUrl'() { if (!Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:connectServer' }); diff --git a/app/setup-wizard/client/setupWizard.js b/app/setup-wizard/client/setupWizard.js index 16b472cee54b..0d6d8dd1e32b 100644 --- a/app/setup-wizard/client/setupWizard.js +++ b/app/setup-wizard/client/setupWizard.js @@ -193,7 +193,19 @@ Template.setupWizard.events({ persistSettings(t.state.all(), () => { localStorage.removeItem('wizard'); localStorage.setItem('wizardFinal', true); - FlowRouter.go('setup-wizard-final'); + + if (t.state.get('registerServer')) { + Meteor.call('cloud:registerWorkspace', (error) => { + if (error) { + console.warn(error); + return; + } + + FlowRouter.go('setup-wizard-final'); + }); + } else { + FlowRouter.go('setup-wizard-final'); + } }); return false; } diff --git a/app/statistics/server/functions/get.js b/app/statistics/server/functions/get.js index 7e06f74e7fa6..2b3b94df8418 100644 --- a/app/statistics/server/functions/get.js +++ b/app/statistics/server/functions/get.js @@ -20,6 +20,7 @@ const wizardFields = [ 'Language', 'Server_Type', 'Allow_Marketing_Emails', + 'Register_Server', ]; statistics.get = function _getStatistics() { From 89ea227065ff99256ffc7dc009e36ea3959f4354 Mon Sep 17 00:00:00 2001 From: Aaron Ogle Date: Wed, 20 Mar 2019 18:12:40 -0500 Subject: [PATCH 2/5] add sync and fix i18n and a few other issues --- app/cloud/client/admin/cloud.html | 31 +++++++--- app/cloud/client/admin/cloud.js | 61 +++++++++++++++---- .../functions/getWorkspaceAccessToken.js | 4 +- .../functions/startRegisterWorkspace.js | 12 ++-- app/cloud/server/functions/syncWorkspace.js | 58 ++++++++++++++++++ .../server/functions/unregisterWorkspace.js | 12 ++-- app/cloud/server/methods.js | 18 ++++-- packages/rocketchat-i18n/i18n/en.i18n.json | 7 ++- 8 files changed, 162 insertions(+), 41 deletions(-) create mode 100644 app/cloud/server/functions/syncWorkspace.js diff --git a/app/cloud/client/admin/cloud.html b/app/cloud/client/admin/cloud.html index 3fb5cd1a9178..12225ab37f87 100644 --- a/app/cloud/client/admin/cloud.html +++ b/app/cloud/client/admin/cloud.html @@ -19,23 +19,38 @@ {{#if info.connectToCloud}} {{#if info.workspaceRegistered}}
- - {{#if info.userAssociated}} -

{{_ "Cloud_workspace_connected_plus_account"}}

- {{else}} -

{{_ "Cloud_workspace_connected_without_account"}}

+

{{_ "Cloud_workspace_connected"}}

+ +
+ +
+

{{_ "Cloud_workspace_support"}}

- +
- {{/if}} +
+ +
+

{{_ "Cloud_workspace_disconnect"}}

+
+ +
+ +
+
{{else}}
- +
{{_ "Cloud_address_to_send_registration_to"}}
diff --git a/app/cloud/client/admin/cloud.js b/app/cloud/client/admin/cloud.js index e0d669323b55..1975e12fc8c4 100644 --- a/app/cloud/client/admin/cloud.js +++ b/app/cloud/client/admin/cloud.js @@ -34,7 +34,7 @@ Template.cloud.onCreated(function() { } if (!success) { - toastr.error('Invalid token'); + toastr.error('An error occured connecting'); instance.loadRegStatus(); return; } @@ -45,6 +45,46 @@ Template.cloud.onCreated(function() { }); }; + instance.disconnectWorkspace = function _disconnectWorkspace() { + Meteor.call('cloud:disconnectWorkspace', (error, success) => { + if (error) { + toastr.error(error); + instance.loadRegStatus(); + return; + } + + if (!success) { + toastr.error('An error occured disconnecting'); + instance.loadRegStatus(); + return; + } + + toastr.success(t('Disconnected')); + + instance.loadRegStatus(); + }); + }; + + instance.syncWorkspace = function _syncWorkspace() { + Meteor.call('cloud:syncWorkspace', (error, success) => { + if (error) { + toastr.error(error); + instance.loadRegStatus(); + return; + } + + if (!success) { + toastr.error('An error occured syncing'); + instance.loadRegStatus(); + return; + } + + toastr.success(t('Sync Complete')); + + instance.loadRegStatus(); + }); + }; + instance.registerWorkspace = function _registerWorkspace() { Meteor.call('cloud:registerWorkspace', (error, success) => { if (error) { @@ -94,17 +134,6 @@ Template.cloud.events({ }); }, - 'click .login-btn'() { - Meteor.call('cloud:getOAuthAuthorizationUrl', (error, url) => { - if (error) { - console.warn(error); - return; - } - - window.location.href = url; - }); - }, - 'click .connect-btn'(e, i) { const token = $('input[name=cloudToken]').val(); @@ -114,4 +143,12 @@ Template.cloud.events({ 'click .register-btn'(e, i) { i.registerWorkspace(); }, + + 'click .disconnect-btn'(e, i) { + i.disconnectWorkspace(); + }, + + 'click .sync-btn'(e, i) { + i.syncWorkspace(); + }, }); diff --git a/app/cloud/server/functions/getWorkspaceAccessToken.js b/app/cloud/server/functions/getWorkspaceAccessToken.js index aea0be8528e3..93311609651d 100644 --- a/app/cloud/server/functions/getWorkspaceAccessToken.js +++ b/app/cloud/server/functions/getWorkspaceAccessToken.js @@ -8,7 +8,7 @@ import { getRedirectUri } from './getRedirectUri'; import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; import { unregisterWorkspace } from './unregisterWorkspace'; -export function getWorkspaceAccessToken() { +export function getWorkspaceAccessToken(forceNew = false) { const { connectToCloud, workspaceRegistered } = retrieveRegistrationStatus(); if (!connectToCloud || !workspaceRegistered) { @@ -23,7 +23,7 @@ export function getWorkspaceAccessToken() { const expires = Settings.findOneById('Cloud_Workspace_Access_Token_Expires_At'); const now = new Date(); - if (now < expires.value) { + if (now < expires.value && !forceNew) { return settings.get('Cloud_Workspace_Access_Token'); } diff --git a/app/cloud/server/functions/startRegisterWorkspace.js b/app/cloud/server/functions/startRegisterWorkspace.js index 9521fe6aac80..609e380b43ce 100644 --- a/app/cloud/server/functions/startRegisterWorkspace.js +++ b/app/cloud/server/functions/startRegisterWorkspace.js @@ -7,12 +7,16 @@ import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; import { statistics } from '../../../statistics'; export function startRegisterWorkspace() { - const { workspaceConnected } = retrieveRegistrationStatus(); - if (workspaceConnected || process.env.TEST_MODE) { + const { workspaceRegistered, connectToCloud } = retrieveRegistrationStatus(); + if ((workspaceRegistered && connectToCloud) || process.env.TEST_MODE) { return true; } - Settings.updateValueById('Register_Server', true); + settings.updateById('Register_Server', true); + + if (workspaceRegistered) { + return true; + } const stats = statistics.get(); @@ -34,8 +38,6 @@ export function startRegisterWorkspace() { version: stats.version, }; - console.log(regInfo); - const cloudUrl = settings.get('Cloud_Url'); let result; diff --git a/app/cloud/server/functions/syncWorkspace.js b/app/cloud/server/functions/syncWorkspace.js new file mode 100644 index 000000000000..9cfd21104820 --- /dev/null +++ b/app/cloud/server/functions/syncWorkspace.js @@ -0,0 +1,58 @@ +import { HTTP } from 'meteor/http'; +import { settings } from '../../../settings'; + +import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; +import { getWorkspaceAccessToken } from './getWorkspaceAccessToken'; + +import { statistics } from '../../../statistics'; +import { getWorkspaceLicense } from './getWorkspaceLicense'; + +export function syncWorkspace() { + const { workspaceRegistered, connectToCloud } = retrieveRegistrationStatus(); + if (!workspaceRegistered || !connectToCloud) { + return false; + } + + const stats = statistics.get(); + + const address = settings.get('Site_Url'); + + const info = { + uniqueId: stats.uniqueId, + address, + contactName: stats.wizard.contactName, + contactEmail: stats.wizard.contactEmail, + accountName: stats.wizard.organizationName, + siteName: stats.wizard.siteName, + deploymentMethod: stats.deploy.method, + deploymentPlatform: stats.deploy.platform, + version: stats.version, + }; + + const workspaceUrl = settings.get('Cloud_Workspace_Registration_Client_Uri'); + + try { + const headers = {}; + const token = getWorkspaceAccessToken(true); + + if (token) { + headers.Authorization = `Bearer ${ token }`; + } else { + return false; + } + + HTTP.post(`${ workspaceUrl }/registration`, { + data: info, + headers, + }); + + } catch (e) { + if (e.response && e.response.data && e.response.data.errorCode) { + console.error(`Failed to sync with Rocket.Chat Cloud. ErrorCode: ${ e.response.data.errorCode }`); + } + + return false; + } + + return getWorkspaceLicense(); +} diff --git a/app/cloud/server/functions/unregisterWorkspace.js b/app/cloud/server/functions/unregisterWorkspace.js index 93fb76fe61f5..0415ea4fcab0 100644 --- a/app/cloud/server/functions/unregisterWorkspace.js +++ b/app/cloud/server/functions/unregisterWorkspace.js @@ -8,12 +8,12 @@ export function unregisterWorkspace() { return true; } - Settings.removeById('Cloud_Workspace_Id'); - Settings.removeById('Cloud_Workspace_Name'); - Settings.removeById('Cloud_Workspace_Client_Id'); - Settings.removeById('Cloud_Workspace_Client_Secret'); - Settings.removeById('Cloud_Workspace_Client_Secret_Expires_At'); - Settings.removeById('Cloud_Workspace_Registration_Client_Uri'); + Settings.updateValueById('Cloud_Workspace_Id', null); + Settings.updateValueById('Cloud_Workspace_Name', null); + Settings.updateValueById('Cloud_Workspace_Client_Id', null); + Settings.updateValueById('Cloud_Workspace_Client_Secret', null); + Settings.updateValueById('Cloud_Workspace_Client_Secret_Expires_At', null); + Settings.updateValueById('Cloud_Workspace_Registration_Client_Uri', null); // So doesn't try to register again automatically Settings.updateValueById('Register_Server', false); diff --git a/app/cloud/server/methods.js b/app/cloud/server/methods.js index 402c321fa3bd..09c7498fab07 100644 --- a/app/cloud/server/methods.js +++ b/app/cloud/server/methods.js @@ -9,7 +9,7 @@ import { getOAuthAuthorizationUrl } from './functions/getOAuthAuthorizationUrl'; import { finishOAuthAuthorization } from './functions/finishOAuthAuthorization'; import { startRegisterWorkspace } from './functions/startRegisterWorkspace'; import { disconnectWorkspace } from './functions/disconnectWorkspace'; -import { settings } from '../../settings/server'; +import { syncWorkspace } from './functions/syncWorkspace'; Meteor.methods({ 'cloud:checkRegisterStatus'() { @@ -45,15 +45,20 @@ Meteor.methods({ throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:updateEmail' }); } - const existingEmail = settings.get('Organization_Email'); + Settings.updateValueById('Organization_Email', email); - if (email !== existingEmail) { - Settings.updateValueById('Organization_Email', email); + return startRegisterWorkspace(); + }, + 'cloud:syncWorkspace'() { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:updateEmail' }); + } - return startRegisterWorkspace(); + if (!hasPermission(Meteor.userId(), 'manage-cloud')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:updateEmail' }); } - return; + return syncWorkspace(); }, 'cloud:connectWorkspace'(token) { check(token, String); @@ -79,6 +84,7 @@ Meteor.methods({ return disconnectWorkspace(); }, + // Currently unused but will link local account to Rocket.Chat Cloud account. 'cloud:getOAuthAuthorizationUrl'() { if (!Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:connectServer' }); diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index da291a4c8c90..e4c3c44c8a3e 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -599,8 +599,9 @@ "Cloud_connect": "Rocket.Chat Cloud Connect", "Cloud_what_is_it": "What is this?", "Cloud_what_is_it_description": "Rocket.Chat Cloud Connect allows you to connect your self-hosted Rocket.Chat Workspace to our Cloud. Doing so enables you to manage your licenses, Billing and Support in Rocket.Chat Cloud.", - "Cloud_workspace_connected_plus_account": "Your workspace is now connected to the Rocket.Chat Cloud and an account is associated.", - "Cloud_workspace_connected_without_account": "Your workspace is now connected to the Rocket.Chat Cloud. If you would like, you can login to the Rocket.Chat Cloud and associate your workspace with your Cloud account.", + "Cloud_workspace_connected": "Your workspace has been successfully connected to Rocket.Chat Cloud. You can access the cloud to manage account information", + "Cloud_workspace_support": "If you have any trouble with a cloud service please try to sync first. If the issue persists please you can open a support ticket in the Cloud Console.", + "Cloud_workspace_disconnect": "If you no longer wish to receive cloud services you can disconnect your workspace from the Rocket.Chat Cloud.", "Cloud_login_to_cloud": "Login to Rocket.Chat Cloud", "Cloud_address_to_send_registration_to": "The address to send your Cloud registration email to.", "Cloud_update_email": "Update Email", @@ -1034,6 +1035,7 @@ "Disabled": "Disabled", "Disallow_reacting": "Disallow Reacting", "Disallow_reacting_Description": "Disallows reacting", + "Disconnect": "Disconnect", "Display_offline_form": "Display Offline Form", "Display_unread_counter": "Display number of unread messages", "Displays_action_text": "Displays action text", @@ -2696,6 +2698,7 @@ "Survey": "Survey", "Survey_instructions": "Rate each question according to your satisfaction, 1 meaning you are completely unsatisfied and 5 meaning you are completely satisfied.", "Symbols": "Symbols", + "Sync": "Sync", "Sync / Import": "Sync / Import", "Sync_in_progress": "Synchronization in progress", "Sync_Interval": "Sync interval", From d42c209f3d7ada183661cfb66b7d5133b788facd Mon Sep 17 00:00:00 2001 From: Aaron Ogle Date: Tue, 26 Mar 2019 18:06:20 -0500 Subject: [PATCH 3/5] set oauth scopes --- app/cloud/server/functions/connectWorkspace.js | 2 ++ .../functions/finishOAuthAuthorization.js | 4 ++++ .../functions/getOAuthAuthorizationUrl.js | 5 ++++- .../functions/getWorkspaceAccessToken.js | 18 +++++++++++++----- app/cloud/server/oauthScopes.js | 15 +++++++++++++++ 5 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 app/cloud/server/oauthScopes.js diff --git a/app/cloud/server/functions/connectWorkspace.js b/app/cloud/server/functions/connectWorkspace.js index 6d2e00cbeb88..09126a6e317c 100644 --- a/app/cloud/server/functions/connectWorkspace.js +++ b/app/cloud/server/functions/connectWorkspace.js @@ -6,6 +6,7 @@ import { Settings } from '../../../models'; import { getRedirectUri } from './getRedirectUri'; import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; +import { workspaceScopes } from '../oauthScopes'; export function connectWorkspace(token) { const { connectToCloud } = retrieveRegistrationStatus(); @@ -56,6 +57,7 @@ export function connectWorkspace(token) { client_id: data.client_id, client_secret: data.client_secret, grant_type: 'client_credentials', + scope: workspaceScopes.join(' '), redirect_uri: redirectUri, }), }); diff --git a/app/cloud/server/functions/finishOAuthAuthorization.js b/app/cloud/server/functions/finishOAuthAuthorization.js index c6bb4b343285..1187a1c2e3d6 100644 --- a/app/cloud/server/functions/finishOAuthAuthorization.js +++ b/app/cloud/server/functions/finishOAuthAuthorization.js @@ -6,6 +6,7 @@ import { settings } from '../../../settings'; import { Settings, Users } from '../../../models'; import { getRedirectUri } from './getRedirectUri'; +import { userScopes } from '../oauthScopes'; export function finishOAuthAuthorization(code, state) { if (settings.get('Cloud_Workspace_Registration_State') !== state) { @@ -16,6 +17,8 @@ export function finishOAuthAuthorization(code, state) { const clientId = settings.get('Cloud_Workspace_Client_Id'); const clientSecret = settings.get('Cloud_Workspace_Client_Secret'); + const scope = userScopes.join(' '); + let result; try { result = HTTP.post(`${ cloudUrl }/api/oauth/token`, { @@ -24,6 +27,7 @@ export function finishOAuthAuthorization(code, state) { client_id: clientId, client_secret: clientSecret, grant_type: 'authorization_code', + scope, code, redirect_uri: getRedirectUri(), }), diff --git a/app/cloud/server/functions/getOAuthAuthorizationUrl.js b/app/cloud/server/functions/getOAuthAuthorizationUrl.js index 9da757fb9a2c..b0f3b687989c 100644 --- a/app/cloud/server/functions/getOAuthAuthorizationUrl.js +++ b/app/cloud/server/functions/getOAuthAuthorizationUrl.js @@ -3,6 +3,7 @@ import { Settings } from '../../../models'; import { settings } from '../../../settings'; import { getRedirectUri } from './getRedirectUri'; +import { userScopes } from '../oauthScopes'; export function getOAuthAuthorizationUrl() { const state = Random.id(); @@ -13,5 +14,7 @@ export function getOAuthAuthorizationUrl() { const client_id = settings.get('Cloud_Workspace_Client_Id'); const redirectUri = getRedirectUri(); - return `${ cloudUrl }/authorize?response_type=code&client_id=${ client_id }&redirect_uri=${ redirectUri }&scope=offline_access&state=${ state }`; + const scope = userScopes.join(' '); + + return `${ cloudUrl }/authorize?response_type=code&client_id=${ client_id }&redirect_uri=${ redirectUri }&scope=${ scope }&state=${ state }`; } diff --git a/app/cloud/server/functions/getWorkspaceAccessToken.js b/app/cloud/server/functions/getWorkspaceAccessToken.js index 93311609651d..8ac0de049995 100644 --- a/app/cloud/server/functions/getWorkspaceAccessToken.js +++ b/app/cloud/server/functions/getWorkspaceAccessToken.js @@ -7,8 +7,9 @@ import { Settings } from '../../../models'; import { getRedirectUri } from './getRedirectUri'; import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; import { unregisterWorkspace } from './unregisterWorkspace'; +import { workspaceScopes } from '../oauthScopes'; -export function getWorkspaceAccessToken(forceNew = false) { +export function getWorkspaceAccessToken(forceNew = false, scope = '', save = true) { const { connectToCloud, workspaceRegistered } = retrieveRegistrationStatus(); if (!connectToCloud || !workspaceRegistered) { @@ -31,6 +32,10 @@ export function getWorkspaceAccessToken(forceNew = false) { const client_secret = settings.get('Cloud_Workspace_Client_Secret'); const redirectUri = getRedirectUri(); + if (scope === '') { + scope = workspaceScopes.join(' '); + } + let authTokenResult; try { authTokenResult = HTTP.post(`${ cloudUrl }/api/oauth/token`, { @@ -38,6 +43,7 @@ export function getWorkspaceAccessToken(forceNew = false) { query: querystring.stringify({ client_id, client_secret, + scope, grant_type: 'client_credentials', redirect_uri: redirectUri, }), @@ -51,11 +57,13 @@ export function getWorkspaceAccessToken(forceNew = false) { return ''; } - const expiresAt = new Date(); - expiresAt.setSeconds(expiresAt.getSeconds() + authTokenResult.data.expires_in); + if (save) { + const expiresAt = new Date(); + expiresAt.setSeconds(expiresAt.getSeconds() + authTokenResult.data.expires_in); - Settings.updateValueById('Cloud_Workspace_Access_Token', authTokenResult.data.access_token); - Settings.updateValueById('Cloud_Workspace_Access_Token_Expires_At', expiresAt); + Settings.updateValueById('Cloud_Workspace_Access_Token', authTokenResult.data.access_token); + Settings.updateValueById('Cloud_Workspace_Access_Token_Expires_At', expiresAt); + } return authTokenResult.data.access_token; } diff --git a/app/cloud/server/oauthScopes.js b/app/cloud/server/oauthScopes.js new file mode 100644 index 000000000000..6152f1c4021e --- /dev/null +++ b/app/cloud/server/oauthScopes.js @@ -0,0 +1,15 @@ +// These are the scopes we by default request access to +export const workspaceScopes = [ + 'workspace:license:read', + 'workspace:client:write', + 'workspace:stats:write', + 'workspace:push:send', + 'marketplace:read', + 'marketplace:download', +]; + +// These are the scopes we use for the user +export const userScopes = [ + 'openid', + 'offline_access', +]; From 4669ab4cc9d5c0d6d4afed6faa6f3909a6c0b68a Mon Sep 17 00:00:00 2001 From: Aaron Ogle Date: Wed, 27 Mar 2019 02:03:27 -0500 Subject: [PATCH 4/5] add suggested grammar and federation hub scope --- app/cloud/server/oauthScopes.js | 1 + packages/rocketchat-i18n/i18n/en.i18n.json | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/cloud/server/oauthScopes.js b/app/cloud/server/oauthScopes.js index 6152f1c4021e..3c7dd0813c49 100644 --- a/app/cloud/server/oauthScopes.js +++ b/app/cloud/server/oauthScopes.js @@ -6,6 +6,7 @@ export const workspaceScopes = [ 'workspace:push:send', 'marketplace:read', 'marketplace:download', + 'fedhub:register', ]; // These are the scopes we use for the user diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index bf34cfbf8135..9b797a2cd727 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -600,8 +600,8 @@ "Cloud_what_is_it": "What is this?", "Cloud_what_is_it_description": "Rocket.Chat Cloud Connect allows you to connect your self-hosted Rocket.Chat Workspace to our Cloud. Doing so enables you to manage your licenses, Billing and Support in Rocket.Chat Cloud.", "Cloud_workspace_connected": "Your workspace has been successfully connected to Rocket.Chat Cloud. You can access the cloud to manage account information", - "Cloud_workspace_support": "If you have any trouble with a cloud service please try to sync first. If the issue persists please you can open a support ticket in the Cloud Console.", - "Cloud_workspace_disconnect": "If you no longer wish to receive cloud services you can disconnect your workspace from the Rocket.Chat Cloud.", + "Cloud_workspace_support": "If you have any trouble with a cloud service, please try to sync first. Should the issue persist, please open a support ticket in the Cloud Console.", + "Cloud_workspace_disconnect": "If you no longer wish to utilize cloud services you can disconnect your workspace from the Rocket.Chat Cloud.", "Cloud_login_to_cloud": "Login to Rocket.Chat Cloud", "Cloud_address_to_send_registration_to": "The address to send your Cloud registration email to.", "Cloud_update_email": "Update Email", @@ -3172,4 +3172,4 @@ "Your_question": "Your question", "Your_server_link": "Your server link", "Your_workspace_is_ready": "Your workspace is ready to use 🎉" -} \ No newline at end of file +} From 7e17c5228755e52fc7ec30049fda7d300ada40d6 Mon Sep 17 00:00:00 2001 From: Aaron Ogle Date: Thu, 28 Mar 2019 17:30:07 -0500 Subject: [PATCH 5/5] Switch to postform instead of query string to post oauth more securely --- .../server/functions/connectWorkspace.js | 29 +++++-------------- .../functions/getWorkspaceAccessToken.js | 18 +++++++----- app/cloud/server/functions/syncWorkspace.js | 4 +-- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/app/cloud/server/functions/connectWorkspace.js b/app/cloud/server/functions/connectWorkspace.js index 09126a6e317c..e12c5fadf416 100644 --- a/app/cloud/server/functions/connectWorkspace.js +++ b/app/cloud/server/functions/connectWorkspace.js @@ -1,12 +1,10 @@ -import querystring from 'querystring'; - import { HTTP } from 'meteor/http'; import { settings } from '../../../settings'; import { Settings } from '../../../models'; import { getRedirectUri } from './getRedirectUri'; import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; -import { workspaceScopes } from '../oauthScopes'; +import { getWorkspaceAccessToken } from './getWorkspaceAccessToken'; export function connectWorkspace(token) { const { connectToCloud } = retrieveRegistrationStatus(); @@ -32,6 +30,10 @@ export function connectWorkspace(token) { data: regInfo, }); } catch (e) { + if (e.response && e.response.data && e.response.data.error) { + console.error(`Failed to register with Rocket.Chat Cloud. Error: ${ e.response.data.error }`); + } + return false; } @@ -49,27 +51,10 @@ export function connectWorkspace(token) { Settings.updateValueById('Cloud_Workspace_Registration_Client_Uri', data.registration_client_uri); // Now that we have the client id and secret, let's get the access token - let authTokenResult; - try { - authTokenResult = HTTP.post(`${ cloudUrl }/api/oauth/token`, { - data: {}, - query: querystring.stringify({ - client_id: data.client_id, - client_secret: data.client_secret, - grant_type: 'client_credentials', - scope: workspaceScopes.join(' '), - redirect_uri: redirectUri, - }), - }); - } catch (e) { + const accessToken = getWorkspaceAccessToken(true); + if (!accessToken) { return false; } - const expiresAt = new Date(); - expiresAt.setSeconds(expiresAt.getSeconds() + authTokenResult.data.expires_in); - - Settings.updateValueById('Cloud_Workspace_Access_Token', authTokenResult.data.access_token); - Settings.updateValueById('Cloud_Workspace_Access_Token_Expires_At', expiresAt); - return true; } diff --git a/app/cloud/server/functions/getWorkspaceAccessToken.js b/app/cloud/server/functions/getWorkspaceAccessToken.js index 8ac0de049995..282479342fd8 100644 --- a/app/cloud/server/functions/getWorkspaceAccessToken.js +++ b/app/cloud/server/functions/getWorkspaceAccessToken.js @@ -1,5 +1,3 @@ -import querystring from 'querystring'; - import { HTTP } from 'meteor/http'; import { settings } from '../../../settings'; import { Settings } from '../../../models'; @@ -39,19 +37,23 @@ export function getWorkspaceAccessToken(forceNew = false, scope = '', save = tru let authTokenResult; try { authTokenResult = HTTP.post(`${ cloudUrl }/api/oauth/token`, { - data: {}, - query: querystring.stringify({ + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + params: { client_id, client_secret, scope, grant_type: 'client_credentials', redirect_uri: redirectUri, - }), + }, }); } catch (e) { - if (e.response && e.response.data && e.response.data.errorCode === 'oauth_invalid_client_credentials') { - console.error('Server has been unregistered from cloud'); - unregisterWorkspace(); + if (e.response && e.response.data && e.response.data.error) { + console.error(`Failed to get AccessToken from Rocket.Chat Cloud. Error: ${ e.response.data.error }`); + + if (e.response.data.error === 'oauth_invalid_client_credentials') { + console.error('Server has been unregistered from cloud'); + unregisterWorkspace(); + } } return ''; diff --git a/app/cloud/server/functions/syncWorkspace.js b/app/cloud/server/functions/syncWorkspace.js index 9cfd21104820..dc285dff31e3 100644 --- a/app/cloud/server/functions/syncWorkspace.js +++ b/app/cloud/server/functions/syncWorkspace.js @@ -47,8 +47,8 @@ export function syncWorkspace() { }); } catch (e) { - if (e.response && e.response.data && e.response.data.errorCode) { - console.error(`Failed to sync with Rocket.Chat Cloud. ErrorCode: ${ e.response.data.errorCode }`); + if (e.response && e.response.data && e.response.data.error) { + console.error(`Failed to sync with Rocket.Chat Cloud. Error: ${ e.response.data.error }`); } return false;