UNPKG

@byteowls/capacitor-oauth2

Version:
227 lines 12.1 kB
import { WebPlugin } from '@capacitor/core'; import { WebUtils } from "./web-utils"; export class OAuth2ClientPluginWeb extends WebPlugin { constructor() { super(...arguments); this.loopCount = 2000; this.intervalLength = 100; this.MSG_RETURNED_TO_JS = "Returned to JS:"; } /** * Get a new access token using an existing refresh token. */ async refreshToken(_options) { return new Promise((_resolve, reject) => { reject(new Error("Functionality not implemented for PWAs yet")); }); } async authenticate(options) { const windowOptions = WebUtils.buildWindowOptions(options); // we open the window first to avoid popups being blocked because of // the asynchronous buildWebOptions call this.windowHandle = window.open('', windowOptions.windowTarget, windowOptions.windowOptions); this.webOptions = await WebUtils.buildWebOptions(options); return new Promise((resolve, reject) => { // validate if (!this.webOptions.appId || this.webOptions.appId.length == 0) { reject(new Error("ERR_PARAM_NO_APP_ID")); } else if (!this.webOptions.authorizationBaseUrl || this.webOptions.authorizationBaseUrl.length == 0) { reject(new Error("ERR_PARAM_NO_AUTHORIZATION_BASE_URL")); } else if (!this.webOptions.redirectUrl || this.webOptions.redirectUrl.length == 0) { reject(new Error("ERR_PARAM_NO_REDIRECT_URL")); } else if (!this.webOptions.responseType || this.webOptions.responseType.length == 0) { reject(new Error("ERR_PARAM_NO_RESPONSE_TYPE")); } else { // init internal control params let loopCount = this.loopCount; this.windowClosedByPlugin = false; // open window const authorizationUrl = WebUtils.getAuthorizationUrl(this.webOptions); if (this.webOptions.logsEnabled) { this.doLog("Authorization url: " + authorizationUrl); } if (this.windowHandle) { this.windowHandle.location.href = authorizationUrl; } // wait for redirect and resolve the this.intervalId = window.setInterval(() => { var _a, _b; if (loopCount-- < 0) { this.closeWindow(); } else if (((_a = this.windowHandle) === null || _a === void 0 ? void 0 : _a.closed) && !this.windowClosedByPlugin) { window.clearInterval(this.intervalId); reject(new Error("USER_CANCELLED")); } else { let href = undefined; try { href = (_b = this.windowHandle) === null || _b === void 0 ? void 0 : _b.location.href; } catch (ignore) { // ignore DOMException: Blocked a frame with origin "http://localhost:4200" from accessing a cross-origin frame. } if (href != null && href.indexOf(this.webOptions.redirectUrl) >= 0) { if (this.webOptions.logsEnabled) { this.doLog("Url from Provider: " + href); } let authorizationRedirectUrlParamObj = WebUtils.getUrlParams(href); if (authorizationRedirectUrlParamObj) { if (this.webOptions.logsEnabled) { this.doLog("Authorization response:", authorizationRedirectUrlParamObj); } window.clearInterval(this.intervalId); // check state if (authorizationRedirectUrlParamObj.state === this.webOptions.state) { if (this.webOptions.accessTokenEndpoint) { const self = this; let authorizationCode = authorizationRedirectUrlParamObj.code; if (authorizationCode) { const tokenRequest = new XMLHttpRequest(); tokenRequest.onload = function () { if (this.status === 200) { let accessTokenResponse = JSON.parse(this.response); if (self.webOptions.logsEnabled) { self.doLog("Access token response:", accessTokenResponse); } self.requestResource(accessTokenResponse.access_token, resolve, reject, authorizationRedirectUrlParamObj, accessTokenResponse); } }; tokenRequest.onerror = function () { // always log error because of CORS hint self.doLog("ERR_GENERAL: See client logs. It might be CORS. Status text: " + this.statusText); reject(new Error("ERR_GENERAL")); }; tokenRequest.open("POST", this.webOptions.accessTokenEndpoint, true); tokenRequest.setRequestHeader('accept', 'application/json'); tokenRequest.setRequestHeader('cache-control', 'no-cache'); tokenRequest.setRequestHeader('content-type', 'application/x-www-form-urlencoded'); tokenRequest.send(WebUtils.getTokenEndpointData(this.webOptions, authorizationCode)); } else { reject(new Error("ERR_NO_AUTHORIZATION_CODE")); } this.closeWindow(); } else { // if no accessTokenEndpoint exists request the resource this.requestResource(authorizationRedirectUrlParamObj.access_token, resolve, reject, authorizationRedirectUrlParamObj); } } else { if (this.webOptions.logsEnabled) { this.doLog("State from web options: " + this.webOptions.state); this.doLog("State returned from provider: " + authorizationRedirectUrlParamObj.state); } reject(new Error("ERR_STATES_NOT_MATCH")); this.closeWindow(); } } // this is no error no else clause required } } }, this.intervalLength); } }); } requestResource(accessToken, resolve, reject, authorizationResponse, accessTokenResponse = null) { if (this.webOptions.resourceUrl) { const logsEnabled = this.webOptions.logsEnabled; if (logsEnabled) { this.doLog("Resource url: " + this.webOptions.resourceUrl); } if (accessToken) { if (logsEnabled) { this.doLog("Access token:", accessToken); } const self = this; const request = new XMLHttpRequest(); request.onload = function () { if (this.status === 200) { let resp = JSON.parse(this.response); if (logsEnabled) { self.doLog("Resource response:", resp); } if (resp) { self.assignResponses(resp, accessToken, authorizationResponse, accessTokenResponse); } if (logsEnabled) { self.doLog(self.MSG_RETURNED_TO_JS, resp); } resolve(resp); } else { reject(new Error(this.statusText)); } self.closeWindow(); }; request.onerror = function () { if (logsEnabled) { self.doLog("ERR_GENERAL: " + this.statusText); } reject(new Error("ERR_GENERAL")); self.closeWindow(); }; request.open("GET", this.webOptions.resourceUrl, true); request.setRequestHeader('Authorization', `Bearer ${accessToken}`); if (this.webOptions.additionalResourceHeaders) { for (const key in this.webOptions.additionalResourceHeaders) { request.setRequestHeader(key, this.webOptions.additionalResourceHeaders[key]); } } request.send(); } else { if (logsEnabled) { this.doLog("No accessToken was provided although you configured a resourceUrl. Remove the resourceUrl from the config."); } reject(new Error("ERR_NO_ACCESS_TOKEN")); this.closeWindow(); } } else { // if no resource url exists just return the accessToken response const resp = {}; this.assignResponses(resp, accessToken, authorizationResponse, accessTokenResponse); if (this.webOptions.logsEnabled) { this.doLog(this.MSG_RETURNED_TO_JS, resp); } resolve(resp); this.closeWindow(); } } assignResponses(resp, accessToken, authorizationResponse, accessTokenResponse = null) { // #154 if (authorizationResponse) { resp["authorization_response"] = authorizationResponse; } if (accessTokenResponse) { resp["access_token_response"] = accessTokenResponse; } resp["access_token"] = accessToken; } async logout(options) { return new Promise((resolve, _reject) => { localStorage.removeItem(WebUtils.getAppId(options)); resolve(true); }); } closeWindow() { var _a; window.clearInterval(this.intervalId); // #164 if the provider's login page is opened in the same tab or window it must not be closed // if (this.webOptions.windowTarget !== "_self") { // this.windowHandle?.close(); // } (_a = this.windowHandle) === null || _a === void 0 ? void 0 : _a.close(); this.windowClosedByPlugin = true; } doLog(msg, obj = null) { console.log("I/Capacitor/OAuth2ClientPlugin: " + msg, obj); } } //# sourceMappingURL=web.js.map