diff --git a/.changeset/shy-dolls-protect.md b/.changeset/shy-dolls-protect.md new file mode 100644 index 0000000000000..9295c3eac9a22 --- /dev/null +++ b/.changeset/shy-dolls-protect.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes an issue where OAuth login buttons were not showing up on the login page diff --git a/apps/meteor/app/apple/client/index.ts b/apps/meteor/app/apple/client/index.ts index 2c59dbe5b3d45..f2579fed790d6 100644 --- a/apps/meteor/app/apple/client/index.ts +++ b/apps/meteor/app/apple/client/index.ts @@ -1,4 +1,4 @@ import { CustomOAuth } from '../../custom-oauth/client/CustomOAuth'; import { config } from '../lib/config'; -new CustomOAuth('apple', config); +CustomOAuth.configureOAuthService('apple', config); diff --git a/apps/meteor/app/custom-oauth/client/CustomOAuth.ts b/apps/meteor/app/custom-oauth/client/CustomOAuth.ts index 7849498fa4b9a..36b02abb5ca50 100644 --- a/apps/meteor/app/custom-oauth/client/CustomOAuth.ts +++ b/apps/meteor/app/custom-oauth/client/CustomOAuth.ts @@ -18,6 +18,8 @@ import { isURL } from '../../../lib/utils/isURL'; // completion. Takes one argument, credentialToken on success, or Error on // error. +const configuredOAuthServices = new Map(); + export class CustomOAuth implements IOAuthProvider { public serverURL: string; @@ -122,4 +124,32 @@ export class CustomOAuth implements IOAuthProvider { }, }); } + + static configureOAuthService(serviceName: string, options: OauthConfig): CustomOAuth { + const existingInstance = configuredOAuthServices.get(serviceName); + if (existingInstance) { + existingInstance.configure(options); + return existingInstance; + } + + // If we don't have a reference to the instance for this service and it was already registered on meteor, + // then there's nothing we can do to update it + if (Accounts.oauth.serviceNames().includes(serviceName)) { + throw new Error(`CustomOAuth service [${serviceName}] already registered, skipping new configuration.`); + } + + const instance = new CustomOAuth(serviceName, options); + configuredOAuthServices.set(serviceName, instance); + return instance; + } + + static configureCustomOAuthService(serviceName: string, options: OauthConfig): CustomOAuth | undefined { + // Custom OAuth services are configured based on the login service list, so if this ends up being called multiple times, simply ignore it + // Non-Custom OAuth services are configured based on code, so if configureOAuthService is called multiple times for them, it's a bug and it should throw. + try { + return this.configureOAuthService(serviceName, options); + } catch (e) { + console.error(e); + } + } } diff --git a/apps/meteor/app/dolphin/client/hooks/useDolphin.ts b/apps/meteor/app/dolphin/client/hooks/useDolphin.ts index 7a9d700238c95..7ae3266f5a824 100644 --- a/apps/meteor/app/dolphin/client/hooks/useDolphin.ts +++ b/apps/meteor/app/dolphin/client/hooks/useDolphin.ts @@ -16,7 +16,7 @@ const config = { accessTokenParam: 'access_token', }; -const Dolphin = new CustomOAuth('dolphin', config); +const Dolphin = CustomOAuth.configureOAuthService('dolphin', config); export const useDolphin = () => { const enabled = useSetting('Accounts_OAuth_Dolphin'); diff --git a/apps/meteor/app/drupal/client/hooks/useDrupal.ts b/apps/meteor/app/drupal/client/hooks/useDrupal.ts index 33295b33684a3..f4808caa78b93 100644 --- a/apps/meteor/app/drupal/client/hooks/useDrupal.ts +++ b/apps/meteor/app/drupal/client/hooks/useDrupal.ts @@ -23,7 +23,7 @@ const config: OauthConfig = { accessTokenParam: 'access_token', }; -const Drupal = new CustomOAuth('drupal', config); +const Drupal = CustomOAuth.configureOAuthService('drupal', config); export const useDrupal = () => { const drupalUrl = useSetting('API_Drupal_URL') as string; diff --git a/apps/meteor/app/github-enterprise/client/hooks/useGitHubEnterpriseAuth.ts b/apps/meteor/app/github-enterprise/client/hooks/useGitHubEnterpriseAuth.ts index f61af7bde79e2..79af68ef234aa 100644 --- a/apps/meteor/app/github-enterprise/client/hooks/useGitHubEnterpriseAuth.ts +++ b/apps/meteor/app/github-enterprise/client/hooks/useGitHubEnterpriseAuth.ts @@ -18,7 +18,7 @@ const config: OauthConfig = { }, }; -const GitHubEnterprise = new CustomOAuth('github_enterprise', config); +const GitHubEnterprise = CustomOAuth.configureOAuthService('github_enterprise', config); export const useGitHubEnterpriseAuth = () => { const githubApiUrl = useSetting('API_GitHub_Enterprise_URL') as string; diff --git a/apps/meteor/app/gitlab/client/hooks/useGitLabAuth.ts b/apps/meteor/app/gitlab/client/hooks/useGitLabAuth.ts index e5f7c32c701e6..c5776723e4981 100644 --- a/apps/meteor/app/gitlab/client/hooks/useGitLabAuth.ts +++ b/apps/meteor/app/gitlab/client/hooks/useGitLabAuth.ts @@ -16,7 +16,7 @@ const config: OauthConfig = { accessTokenParam: 'access_token', }; -const Gitlab = new CustomOAuth('gitlab', config); +const Gitlab = CustomOAuth.configureOAuthService('gitlab', config); export const useGitLabAuth = () => { const gitlabApiUrl = useSetting('API_Gitlab_URL') as string; diff --git a/apps/meteor/app/nextcloud/client/useNextcloud.ts b/apps/meteor/app/nextcloud/client/useNextcloud.ts index 22151c7ff8c29..580d72e92a42a 100644 --- a/apps/meteor/app/nextcloud/client/useNextcloud.ts +++ b/apps/meteor/app/nextcloud/client/useNextcloud.ts @@ -17,7 +17,7 @@ const config: OauthConfig = { }, }; -const Nextcloud = new CustomOAuth('nextcloud', config); +const Nextcloud = CustomOAuth.configureOAuthService('nextcloud', config); export const useNextcloud = (): void => { const nextcloudURL = useSetting('Accounts_OAuth_Nextcloud_URL') as string; diff --git a/apps/meteor/app/tokenpass/client/hooks/useTokenPassAuth.tsx b/apps/meteor/app/tokenpass/client/hooks/useTokenPassAuth.tsx index 24e31db422260..26dbe3a900e2d 100644 --- a/apps/meteor/app/tokenpass/client/hooks/useTokenPassAuth.tsx +++ b/apps/meteor/app/tokenpass/client/hooks/useTokenPassAuth.tsx @@ -20,7 +20,7 @@ const config: OauthConfig = { accessTokenParam: 'access_token', }; -const Tokenpass = new CustomOAuth('tokenpass', config); +const Tokenpass = CustomOAuth.configureOAuthService('tokenpass', config); export const useTokenPassAuth = () => { const setting = useSetting('API_Tokenpass_URL') as string | undefined; diff --git a/apps/meteor/client/lib/loginServices.ts b/apps/meteor/client/lib/loginServices.ts index ad5ee926ccc78..740e7a1f966c6 100644 --- a/apps/meteor/client/lib/loginServices.ts +++ b/apps/meteor/client/lib/loginServices.ts @@ -49,7 +49,11 @@ class LoginServices extends Emitter { if (state === 'loaded') { this.retries = 0; - this.emit('loaded', services); + try { + this.emit('loaded', services); + } catch (e) { + console.error('Failed to apply loaded listed of login services.', e); + } } } @@ -113,13 +117,15 @@ class LoginServices extends Emitter { return this.serviceButtons; } - public onLoad(callback: (services: LoginServiceConfiguration[]) => void) { + public onLoad(callback: (services: LoginServiceConfiguration[]) => void): () => void { if (this.ready) { - return callback(this.services); + callback(this.services); + return () => undefined; } void this.loadServices(); this.once('loaded', callback); + return () => this.off('loaded', callback); } public async loadServices(): Promise { diff --git a/apps/meteor/client/sidebar/hooks/useCustomOAuth.ts b/apps/meteor/client/sidebar/hooks/useCustomOAuth.ts index b6c4acf6176e4..174cb3173433f 100644 --- a/apps/meteor/client/sidebar/hooks/useCustomOAuth.ts +++ b/apps/meteor/client/sidebar/hooks/useCustomOAuth.ts @@ -4,19 +4,21 @@ import { CustomOAuth } from '../../../app/custom-oauth/client/CustomOAuth'; import { loginServices } from '../../lib/loginServices'; export const useCustomOAuth = () => { - useEffect(() => { - loginServices.onLoad((services) => { - for (const service of services) { - if (!('custom' in service && service.custom)) { - continue; - } + useEffect( + () => + loginServices.onLoad((services) => { + for (const service of services) { + if (!('custom' in service && service.custom)) { + continue; + } - new CustomOAuth(service.service, { - serverURL: service.serverURL, - authorizePath: service.authorizePath, - scope: service.scope, - }); - } - }); - }, []); + CustomOAuth.configureCustomOAuthService(service.service, { + serverURL: service.serverURL, + authorizePath: service.authorizePath, + scope: service.scope, + }); + } + }), + [], + ); }; diff --git a/apps/meteor/client/views/root/hooks/useWordPressOAuth.ts b/apps/meteor/client/views/root/hooks/useWordPressOAuth.ts index f6a539be2d60e..c6b90d741c178 100644 --- a/apps/meteor/client/views/root/hooks/useWordPressOAuth.ts +++ b/apps/meteor/client/views/root/hooks/useWordPressOAuth.ts @@ -13,7 +13,7 @@ const configDefault: OauthConfig = { accessTokenParam: 'access_token', }; -const WordPress = new CustomOAuth('wordpress', configDefault); +const WordPress = CustomOAuth.configureOAuthService('wordpress', configDefault); const configureServerType = ( serverType: string, diff --git a/apps/meteor/definition/externals/meteor/accounts-base.d.ts b/apps/meteor/definition/externals/meteor/accounts-base.d.ts index 29445a5a0218a..cffaa88ad8675 100644 --- a/apps/meteor/definition/externals/meteor/accounts-base.d.ts +++ b/apps/meteor/definition/externals/meteor/accounts-base.d.ts @@ -73,6 +73,8 @@ declare module 'meteor/accounts-base' { ): (credentialTokenOrError?: string | globalThis.Error | Meteor.Error | Meteor.TypedError) => void; function registerService(name: string): void; + + function serviceNames(): string[]; } } }