Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Coerce region endpoints to global authentication endpoint. #38

Merged
merged 1 commit into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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());
}
}
Expand Down
16 changes: 12 additions & 4 deletions src/util.js
Original file line number Diff line number Diff line change
@@ -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(/[/]+$/, '');
};
46 changes: 46 additions & 0 deletions tests/util.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const { describe, it, beforeEach, afterEach } = require('mocha');
const sinon = require('sinon');
const { expect } = require('chai');

const { LoginClient } = require('../src/index');

Check warning on line 5 in tests/util.test.js

View workflow job for this annotation

GitHub Actions / nodejs

'LoginClient' is assigned a value but never used
const windowManager = require('../src/windowManager');

Check warning on line 6 in tests/util.test.js

View workflow job for this annotation

GitHub Actions / nodejs

'windowManager' is assigned a value but never used
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');
});
});
});

Loading