From eb6e16f8248e3e4a1d2ccc8e8494cef2c28ae243 Mon Sep 17 00:00:00 2001 From: Warren Parad Date: Fri, 23 Feb 2024 21:37:48 +0100 Subject: [PATCH] Coerce region endpoints to global authentication endpoint. --- src/index.js | 17 +++++++++-------- src/util.js | 16 ++++++++++++---- tests/util.test.js | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 tests/util.test.js diff --git a/src/index.js b/src/index.js index 8cd8f0f..843054c 100644 --- a/src/index.js +++ b/src/index.js @@ -23,21 +23,22 @@ class LoginClient { * @param {Object} [logger] a configured logger object, optionally `console`, which can used to display debug and warning messages. */ constructor(settings, logger) { - this.settings = Object.assign({ applicationId: 'app_default' }, settings); + const settingsWithDefault = Object.assign({ applicationId: 'app_default' }, settings); this.logger = logger || console; - const hostUrl = this.settings.authressApiUrl || this.settings.authressLoginHostUrl || this.settings.authenticationServiceUrl || ''; + const hostUrl = settingsWithDefault.authressApiUrl || settingsWithDefault.authressLoginHostUrl || settingsWithDefault.authenticationServiceUrl || ''; if (!hostUrl) { throw Error('Missing required property "authressApiUrl" in LoginClient constructor. Custom Authress Domain Host is required.'); } + this.applicationId = settingsWithDefault.applicationId; this.hostUrl = sanitizeUrl(hostUrl); this.httpClient = new HttpClient(this.hostUrl, logger); this.lastSessionCheck = 0; this.enableCredentials = this.getMatchingDomainInfo(this.hostUrl); - if (!settings.skipBackgroundCredentialsCheck) { + if (!settingsWithDefault.skipBackgroundCredentialsCheck) { windowManager.onLoad(async () => { await this.userSessionExists(true); }); @@ -166,7 +167,7 @@ class LoginClient { } const userConfigurationScreenUrl = new URL('/settings', this.hostUrl); - userConfigurationScreenUrl.searchParams.set('client_id', this.settings.applicationId); + userConfigurationScreenUrl.searchParams.set('client_id', this.applicationId); userConfigurationScreenUrl.searchParams.set('start_page', options && options.startPage || 'Profile'); userConfigurationScreenUrl.searchParams.set('redirect_uri', options && options.redirectUrl || windowManager.getCurrentLocation().href); windowManager.assign(userConfigurationScreenUrl.toString()); @@ -309,7 +310,7 @@ class LoginClient { // * This prevents canonical replay attacks, and fall through. If the user is already logged in, then the new log in attempt is ignored. if (authRequest.nonce === urlSearchParams.get('nonce')) { const code = urlSearchParams.get('code') === 'cookie' ? cookieManager.parse(document.cookie)['auth-code'] : urlSearchParams.get('code'); - const request = { grant_type: 'authorization_code', redirect_uri: authRequest.redirectUrl, client_id: this.settings.applicationId, code, code_verifier: authRequest.codeVerifier }; + const request = { grant_type: 'authorization_code', redirect_uri: authRequest.redirectUrl, client_id: this.applicationId, code, code_verifier: authRequest.codeVerifier }; try { const tokenResult = await this.httpClient.post(`/authentication/${authRequest.nonce}/tokens`, this.enableCredentials, request); const idToken = jwtManager.decode(tokenResult.data.id_token); @@ -522,7 +523,7 @@ class LoginClient { redirectUrl: selectedRedirectUrl, codeChallengeMethod: 'S256', codeChallenge, connectionId, tenantLookupIdentifier, connectionProperties, - applicationId: this.settings.applicationId + applicationId: this.applicationId }, headers); windowManager.assign(requestOptions.data.authenticationUrl); } catch (error) { @@ -578,7 +579,7 @@ class LoginClient { redirectUrl: selectedRedirectUrl, codeChallengeMethod: 'S256', codeChallenge, connectionId, tenantLookupIdentifier, inviteId, connectionProperties, - applicationId: this.settings.applicationId, + applicationId: this.applicationId, responseLocation, flowType, multiAccount }); localStorage.setItem(AuthenticationRequestNonceKey, JSON.stringify({ @@ -651,7 +652,7 @@ class LoginClient { const fullLogoutUrl = new URL('/logout', this.hostUrl); fullLogoutUrl.searchParams.set('redirect_uri', redirectUrl || windowManager.getCurrentLocation().href); - fullLogoutUrl.searchParams.set('client_id', this.settings.applicationId); + fullLogoutUrl.searchParams.set('client_id', this.applicationId); windowManager.assign(fullLogoutUrl.toString()); } } diff --git a/src/util.js b/src/util.js index d27b58d..caf6935 100644 --- a/src/util.js +++ b/src/util.js @@ -1,7 +1,15 @@ -module.exports.sanitizeUrl = function sanitizeUrl(url) { - if (url.startsWith('http')) { - return url; +module.exports.sanitizeUrl = function sanitizeUrl(rawUrlStrng) { + let sanitizedUrl = rawUrlStrng; + if (!sanitizedUrl.startsWith('http')) { + sanitizedUrl = `https://${sanitizedUrl}`; } - return `https://${url}`; + const url = new URL(sanitizedUrl); + const domainBaseUrlMatch = url.host.match(/^([a-z0-9-]+)[.][a-z0-9-]+[.]authress[.]io$/); + if (domainBaseUrlMatch) { + url.host = `${domainBaseUrlMatch[1]}.login.authress.io`; + sanitizedUrl = url.toString(); + } + + return sanitizedUrl.replace(/[/]+$/, ''); }; diff --git a/tests/util.test.js b/tests/util.test.js new file mode 100644 index 0000000..5e27460 --- /dev/null +++ b/tests/util.test.js @@ -0,0 +1,46 @@ +const { describe, it, beforeEach, afterEach } = require('mocha'); +const sinon = require('sinon'); +const { expect } = require('chai'); + +const { LoginClient } = require('../src/index'); +const windowManager = require('../src/windowManager'); +const { sanitizeUrl } = require('../src/util'); + +let sandbox; +beforeEach(() => { sandbox = sinon.createSandbox(); }); +afterEach(() => sandbox.restore()); + +describe('util.js', () => { + describe('sanitizeUrl()', () => { + it('Returns http for localhost', () => { + const authressApiUrl = 'http://localhost:8080'; + const result = sanitizeUrl(authressApiUrl); + expect(result).to.eql('http://localhost:8080'); + }); + + it('Returns http for localstack', () => { + const authressApiUrl = 'http://authress.localstack.cloud:4556'; + const result = sanitizeUrl(authressApiUrl); + expect(result).to.eql('http://authress.localstack.cloud:4556'); + }); + + it('custom domain returns custom domain', () => { + const authressApiUrl = 'https://authress.company.com'; + const result = sanitizeUrl(authressApiUrl); + expect(result).to.eql('https://authress.company.com'); + }); + + it('raw authentication domain returns domain', () => { + const authressApiUrl = 'https://account.login.authress.io'; + const result = sanitizeUrl(authressApiUrl); + expect(result).to.eql('https://account.login.authress.io'); + }); + + it('Convert raw authorization region domain to global authentication. This can be necessary when an account incorrectly uses the authorization domain when really they need to use the authentication one.', () => { + const authressApiUrl = 'https://account.api-na-east.authress.io'; + const result = sanitizeUrl(authressApiUrl); + expect(result).to.eql('https://account.login.authress.io'); + }); + }); +}); +