UNPKG

@casual-simulation/aux-vm-browser

Version:

A set of utilities required to securely run an AUX in a web browser.

559 lines 17.8 kB
import { wrap, proxy } from 'comlink'; import { setupChannel, waitForLoad } from '../html/IFrameHelpers'; import { BehaviorSubject, Subscription } from 'rxjs'; import { hasValue, parseRecordKey } from '@casual-simulation/aux-common'; // Save the query string that was used when the site loaded const query = typeof location !== 'undefined' ? location.search : null; export const WRAPPER_CREATION_TIMEOUT_MS = 2000; /** * Defines a class that helps handle authentication/authorization for a particular endpoint. */ export class AuthEndpointHelper { get currentLoginStatus() { const status = this._loginStatus.value; if (status.authData || status.isLoading || status.isLoggingIn) { return status; } else { return null; } } /** * Creates a new instance of the AuthHelper class. * @param iframeOrigin The URL that the auth iframe should be loaded from. * @param defaultRecordsOrigin The HTTP Origin that should be used for the records origin if the auth site does not support protocol version 4. * @param requirePrivoLogin Whether to require that the user login with Privo. * @param sessionKey The session key that should be used. If not specified, then the stored session key will be used. * @param connectionKey The connection key that should be used. If not specified, then the stored connection key will be used. */ constructor(iframeOrigin, defaultRecordsOrigin, requirePrivoLogin, sessionKey, connectionKey) { this._initialized = false; this._protocolVersion = 1; this._sub = new Subscription(); this._loginStatus = new BehaviorSubject({}); this._loginUIStatus = new BehaviorSubject({ page: false, }); this._authenticating = false; this._origin = iframeOrigin; this._defaultRecordsOrigin = defaultRecordsOrigin; this._requirePrivoLogin = requirePrivoLogin; this._initialSessionKey = sessionKey; this._initialConnectionKey = connectionKey; } get origin() { return this._origin; } get recordsOrigin() { var _a, _b; return ((_b = (_a = this._recordsOrigin) !== null && _a !== void 0 ? _a : this._defaultRecordsOrigin) !== null && _b !== void 0 ? _b : this._origin); } get loginStatus() { return this._loginStatus; } get loginUIStatus() { return this._loginUIStatus; } /** * Gets whether authentication is supported by this inst. */ get supportsAuthentication() { return hasValue(this._origin); } get closed() { var _a; return (_a = this._sub) === null || _a === void 0 ? void 0 : _a.closed; } unsubscribe() { return this.dispose(); } dispose() { if (this._sub) { this._sub.unsubscribe(); this._sub = null; } } async _init() { if (!this._initPromise) { this._initPromise = this._initCore(); } return this._initPromise; } async _initCore() { if (!hasValue(this._origin)) { throw new Error('Cannot initialize AuthHelper because no iframe origin is set.'); } this._loginStatus.next({ isLoading: true, }); const iframeUrl = new URL(`/iframe.html${query}`, this._origin).href; const iframe = (this._iframe = document.createElement('iframe')); let promise = waitForLoad(this._iframe); this._sub.add(() => { iframe.remove(); }); this._iframe.src = iframeUrl; this._iframe.style.display = 'none'; this._iframe.allow = 'publickey-credentials-get *'; this._iframe.className = 'auth-helper-iframe'; document.body.insertBefore(this._iframe, document.body.firstChild); await promise; this._channel = setupChannel(this._iframe.contentWindow); const wrapper = wrap(this._channel.port1); this._proxy = await Promise.race([ new wrapper(this._initialSessionKey, this._initialConnectionKey), new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('Failed to communicate with endpoint.')); }, WRAPPER_CREATION_TIMEOUT_MS); }), ]); try { this._protocolVersion = await this._proxy.getProtocolVersion(); } catch (err) { console.log('[AuthHelper] Could not get protocol version. Defaulting to version 1.'); this._protocolVersion = 1; } if (this._protocolVersion >= 2) { await this._proxy.addLoginUICallback(proxy((status) => { this._loginUIStatus.next(status); })); await this._proxy.addLoginStatusCallback(proxy((status) => { this._loginStatus.next(status); })); } if (this._protocolVersion >= 9) { await this._proxy.addOAuthRedirectCallback(proxy((request) => { this._handleOAuthRedirectCallback(request); })); } if (this._protocolVersion >= 4) { this._recordsOrigin = await this._proxy.getRecordsOrigin(); } if (this._protocolVersion >= 9) { this._websocketOrigin = await this._proxy.getWebsocketOrigin(); this._websocketProtocol = await this._proxy.getWebsocketProtocol(); } this._loginUIStatus.subscribe((status) => { if (!this._iframe) { return; } if (status.page === 'show_iframe') { this._iframe.style.display = null; } else { this._iframe.style.display = 'none'; } }); this._initialized = true; } _handleOAuthRedirectCallback(request) { if (this._newTab && !this._newTab.closed) { this._newTab.location = request.authorizationUrl; } else { console.error('[AuthEndpointHelper] Cannot handle oauth redirect callback.'); } } /** * Determines if the user is authenticated. */ async isAuthenticated() { if (!hasValue(this._origin)) { return false; } if (!this._initialized) { await this._init(); } return await this._isAuthenticatedCore(); } async _isAuthenticatedCore() { return await this._proxy.isLoggedIn(); } async relogin() { if (!hasValue(this._origin)) { return; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 12) { await this._proxy.logout(); await this._proxy.login(true); } else { await this._proxy.relogin(); } } /** * Requests that the user become authenticated if they are not already. */ async authenticate(hint) { if (!hasValue(this._origin)) { return null; } if (!this._initialized) { await this._init(); } if (this._authenticating) { return await this._authenticationPromise; } try { this._authenticating = true; this._authenticationPromise = this._authenticateCore(hint); return await this._authenticationPromise; } finally { this._authenticating = false; } } async _authenticateCore(hint) { if (hint === 'sign in') { this._createNewTab(); } const result = await this._proxy.login(undefined, hint); if (this._protocolVersion < 2) { this._loginStatus.next({ authData: result, }); } return result; } /** * Requests that the user become authenticated entirely in the background. * This will not show any UI to the user but may also mean that the user will not be able to be authenticated. */ async authenticateInBackground() { if (!hasValue(this._origin)) { return null; } if (!this._initialized) { await this._init(); } return await this._authenticateInBackgroundCore(); } async _authenticateInBackgroundCore() { const result = await this._proxy.login(true); if (this._protocolVersion < 2) { this._loginStatus.next({ authData: result, }); } return result; } async createPublicRecordKey(recordName, policy) { if (!hasValue(this._origin)) { return { success: false, errorCode: 'not_supported', errorMessage: 'Records are not supported on this inst.', errorReason: 'not_supported', }; } if (!this._initialized) { await this._init(); } return await this._createPublicRecordKeyCore(recordName, policy); } async _createPublicRecordKeyCore(recordName, policy) { return await this._proxy.createPublicRecordKey(recordName, policy); } async getRecordsOrigin() { var _a, _b; if (!hasValue(this._origin)) { return null; } if (!this._initialized) { await this._init(); } return ((_b = (_a = this._recordsOrigin) !== null && _a !== void 0 ? _a : this._defaultRecordsOrigin) !== null && _b !== void 0 ? _b : this._origin); } async getWebsocketOrigin() { if (!hasValue(this._origin)) { return null; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 9) { return null; } return this._websocketOrigin; } async getWebsocketProtocol() { var _a; if (!hasValue(this._origin)) { return null; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 9) { return null; } return (_a = this._websocketProtocol) !== null && _a !== void 0 ? _a : null; } async getRecordKeyPolicy(recordKey) { const keyInfo = parseRecordKey(recordKey); if (!keyInfo) { return null; } const [name, secret, policy] = keyInfo; return policy; } async getAuthToken() { if (!hasValue(this._origin)) { return null; } if (!this._initialized) { await this._init(); } return await this._getAuthTokenCore(); } async _getAuthTokenCore() { return await this._proxy.getAuthToken(); } async getConnectionKey() { if (!hasValue(this._origin)) { return null; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 6) { return null; } return await this._getConnectionKeyCore(); } async _getConnectionKeyCore() { return await this._proxy.getConnectionKey(); } async openAccountPage() { if (!hasValue(this._origin)) { return; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 2) { return; } if (this._protocolVersion >= 11) { const newTab = window.open('/loading-oauth.html', '_blank'); const url = await this._proxy.getAccountPage(); if (newTab && !newTab.closed) { newTab.location = url; } } else { return await this._proxy.openAccountPage(); } } async setUseCustomUI(useCustomUI) { if (!hasValue(this._origin)) { return; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 2) { return; } return await this._proxy.setUseCustomUI(useCustomUI); } async provideEmailAddress(email, acceptedTermsOfService) { if (!hasValue(this._origin)) { return; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 2) { return; } return await this._proxy.provideEmailAddress(email, acceptedTermsOfService); } async isValidEmailAddress(email) { if (!hasValue(this._origin)) { return; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 9) { return { success: true, allowed: true, }; } return await this._proxy.isValidEmailAddress(email); } async isValidDisplayName(displayName, name) { if (!hasValue(this._origin)) { return; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 9) { return { success: true, allowed: true, }; } return await this._proxy.isValidDisplayName(displayName, name); } async provideSmsNumber(sms, acceptedTermsOfService) { if (!hasValue(this._origin)) { return; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 3) { return; } return await this._proxy.provideSmsNumber(sms, acceptedTermsOfService); } async provideCode(code) { if (!hasValue(this._origin)) { return; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 5) { return; } return await this._proxy.provideCode(code); } async provideLoginResult(result) { if (!hasValue(this._origin)) { return; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 10) { return; } return await this._proxy.provideLoginResult(result); } async providePrivoSignUpInfo(info) { if (!hasValue(this._origin)) { return; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 9) { return; } return await this._proxy.providePrivoSignUpInfo(info); } async provideHasAccount(hasAccount) { if (!hasValue(this._origin)) { return; } if (hasAccount) { this._createNewTab(); } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 9) { return; } return await this._proxy.provideHasAccount(hasAccount); } async cancelLogin() { if (!hasValue(this._origin)) { return; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 2) { return; } return await this._proxy.cancelLogin(); } async logout() { if (!hasValue(this._origin)) { return; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 8) { return; } return await this._logoutCore(); } async _logoutCore() { return await this._proxy.logout(); } async getPolicyUrls() { if (!hasValue(this._origin)) { return { privacyPolicyUrl: null, termsOfServiceUrl: null, codeOfConductUrl: null, supportUrl: null, }; } if (!this._initialized) { await this._init(); } if (this._protocolVersion < 9) { return { privacyPolicyUrl: null, termsOfServiceUrl: null, codeOfConductUrl: null, supportUrl: null, }; } return await this._proxy.getPolicyUrls(); } async getComIdWebConfig(comId) { if (!hasValue(this._origin)) { return null; } if (!this._initialized) { await this._init(); } return await this._proxy.getComIdWebConfig(comId); } async grantPermission(recordName, permission) { if (!hasValue(this._origin)) { return null; } if (!this._initialized) { await this._init(); } return await this._proxy.grantPermission(recordName, permission); } _createNewTab() { if (!this._requirePrivoLogin) { return; } this._newTab = window.open('/loading-oauth.html', '_blank'); if (this._newTab) { if (this._tabCloseInterval) { clearInterval(this._tabCloseInterval); } this._tabCloseInterval = setInterval(() => { var _a; if (!this._newTab || this._newTab.closed) { clearInterval(this._tabCloseInterval); } if ((_a = this._newTab) === null || _a === void 0 ? void 0 : _a.closed) { this._newTab = null; this._proxy.provideOAuthLoginComplete(); } }, 500); } } } //# sourceMappingURL=AuthEndpointHelper.js.map