@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
JavaScript
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