UNPKG

oidc-lib

Version:

A library for creating OIDC Service Providers

1,434 lines (1,210 loc) 71.5 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["credentialHandlerPolyfill"] = factory(); else root["credentialHandlerPolyfill"] = factory(); })(window, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; /******/ var ns = Object.create(null); /******/ __webpack_require__.r(ns); /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = "./index.js"); /******/ }) /************************************************************************/ /******/ ({ /***/ "./CredentialHandler.js": /*!******************************!*\ !*** ./CredentialHandler.js ***! \******************************/ /*! exports provided: CredentialHandler */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialHandler", function() { return CredentialHandler; }); /* harmony import */ var web_request_rpc__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! web-request-rpc */ "./node_modules/web-request-rpc/index.js"); /* harmony import */ var _CredentialHandlerService_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./CredentialHandlerService.js */ "./CredentialHandlerService.js"); /*! * The core CredentialHandler class. * * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. */ /* global DOMException */ const EVENT_TYPES = ['credentialrequest', 'credentialstore']; class CredentialHandler extends web_request_rpc__WEBPACK_IMPORTED_MODULE_0__["WebApp"] { constructor(mediatorOrigin) { if(typeof mediatorOrigin !== 'string') { throw new TypeError('"mediatorOrigin" must be a string.'); } super(mediatorOrigin); this._emitter = new web_request_rpc__WEBPACK_IMPORTED_MODULE_0__["EventEmitter"]({ async waitUntil(event) { // TODO: may need to do `this.hide()` after this promise resolves // to handle case where e.openWindow() was called return event._promise || Promise.reject( new DOMException( 'No "credentialrequest" event handler found.', 'NotFoundError')); } }); } async connect() { const injector = await super.connect(); // define API that CredentialMediator can call on this credential handler this.server.define('credentialHandler', new _CredentialHandlerService_js__WEBPACK_IMPORTED_MODULE_1__["CredentialHandlerService"](this)); // auto-call `ready` await this.ready(); return injector; } addEventListener(eventType, fn) { if(!EVENT_TYPES.includes(eventType)) { throw new DOMException( `Unsupported event type "${eventType}"`, 'NotSupportedError'); } return this._emitter.addEventListener(eventType, fn); } removeEventListener(eventType, fn) { if(!EVENT_TYPES.includes(eventType)) { throw new DOMException( `Unsupported event type "${eventType}"`, 'NotSupportedError'); } return this._emitter.removeEventListener(eventType, fn); } } /***/ }), /***/ "./CredentialHandlerRegistration.js": /*!******************************************!*\ !*** ./CredentialHandlerRegistration.js ***! \******************************************/ /*! exports provided: CredentialHandlerRegistration */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialHandlerRegistration", function() { return CredentialHandlerRegistration; }); /* harmony import */ var _CredentialManager_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./CredentialManager.js */ "./CredentialManager.js"); /*! * A CredentialHandlerRegistration provides a CredentialManager to enable Web * apps to register Profiles that can be presented to websites. * * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. */ class CredentialHandlerRegistration { constructor(url, injector) { if(!(url && typeof url === 'string')) { throw new TypeError('"url" must be a non-empty string.'); } this.credentialManager = new _CredentialManager_js__WEBPACK_IMPORTED_MODULE_0__["CredentialManager"](url, injector); } } /***/ }), /***/ "./CredentialHandlerService.js": /*!*************************************!*\ !*** ./CredentialHandlerService.js ***! \*************************************/ /*! exports provided: CredentialHandlerService */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialHandlerService", function() { return CredentialHandlerService; }); /* harmony import */ var _CredentialRequestEvent_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./CredentialRequestEvent.js */ "./CredentialRequestEvent.js"); /* harmony import */ var _CredentialStoreEvent_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./CredentialStoreEvent.js */ "./CredentialStoreEvent.js"); /*! * A CredentialHandlerService handles remote calls to a CredentialHandler. * * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. */ class CredentialHandlerService { constructor(credentialHandler) { this._credentialHandler = credentialHandler; } async request(credentialRequestEvent) { // TODO: validate credentialRequestEvent return await this._credentialHandler._emitter.emit( new _CredentialRequestEvent_js__WEBPACK_IMPORTED_MODULE_0__["CredentialRequestEvent"](Object.assign( {credentialHandler: this._credentialHandler}, credentialRequestEvent))); } async store(credentialStoreEvent) { // TODO: validate credentialStoreEvent return await this._credentialHandler._emitter.emit( new _CredentialStoreEvent_js__WEBPACK_IMPORTED_MODULE_1__["CredentialStoreEvent"](Object.assign( {credentialHandler: this._credentialHandler}, credentialStoreEvent))); } } /***/ }), /***/ "./CredentialHandlers.js": /*!*******************************!*\ !*** ./CredentialHandlers.js ***! \*******************************/ /*! exports provided: CredentialHandlers */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialHandlers", function() { return CredentialHandlers; }); /* harmony import */ var _CredentialHandlerRegistration_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./CredentialHandlerRegistration.js */ "./CredentialHandlerRegistration.js"); /*! * Provides an API for working with CredentialHandlerRegistrations. * * Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved. */ class CredentialHandlers { constructor(injector) { this._init = (async () => { this._injector = await injector; this._remote = this._injector.get('credentialHandlers', { functions: [ 'register', 'unregister', 'getRegistration', 'hasRegistration'] }); })(); } /** * Creates a credential handler registration. * * @param url the unique URL for the credential handler. * * @return a Promise that resolves to the CredentialHandlerRegistration. */ async register(url) { await this._init; // register with credential mediator url = await this._remote.register('credential', url); return new _CredentialHandlerRegistration_js__WEBPACK_IMPORTED_MODULE_0__["CredentialHandlerRegistration"](url, this._injector); } /** * Unregisters a credential handler, destroying its registration. * * @param url the unique URL for the credential handler. * * @return a Promise that resolves to `true` if the handler was registered * and `false` if not. */ async unregister(url) { await this._init; // unregister with credential mediator return this._remote.unregister('credential', url); } /** * Gets an existing credential handler registration. * * @param url the URL for the credential handler. * * @return a Promise that resolves to the CredentialHandlerRegistration or * `null` if no such registration exists. */ async getRegistration(url) { await this._init; url = await this._remote.getRegistration('credential', url); if(!url) { return null; } return new _CredentialHandlerRegistration_js__WEBPACK_IMPORTED_MODULE_0__["CredentialHandlerRegistration"](url, this._injector); } /** * Returns true if the given credential handler has been registered and * false if not. * * @param url the URL for the credential handler. * * @return a Promise that resolves to `true` if the registration exists and * `false` if not. */ async hasRegistration(url) { await this._init; return await this._remote.hasRegistration('credential', url); } } /***/ }), /***/ "./CredentialHints.js": /*!****************************!*\ !*** ./CredentialHints.js ***! \****************************/ /*! exports provided: CredentialHints */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialHints", function() { return CredentialHints; }); /*! * API for managing CredentialHints. * * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. */ /* global Image */ class CredentialHints { constructor(url, injector) { const remote = injector.get('credentialHints', { functions: ['delete', 'get', 'keys', 'has', 'set', 'clear'] }); for(let methodName in remote) { if(methodName !== 'set') { this[methodName] = remote[methodName].bind(this, url); } } this._remoteSet = remote.set.bind(this, url); } async set(hintKey, credentialHint) { // TODO: validate credential hint // ensure images are prefetched so that they will not leak information // when fetched later credentialHint.icons = credentialHint.icons || []; const promises = credentialHint.icons.map(icon => imageToDataUrl(icon.src).then(fetchedImage => { icon.fetchedImage = fetchedImage; })); await Promise.all(promises); return this._remoteSet(hintKey, credentialHint); } } function imageToDataUrl(url) { return new Promise(resolve => { const img = new Image(); img.crossOrigin = 'Anonymous'; img.onload = () => { let canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.height = img.height; canvas.width = img.width; ctx.drawImage(img, 0, 0); const dataUrl = canvas.toDataURL(); resolve(dataUrl); canvas = null; }; // TODO: `reject` as an error and fail `.set`? img.onerror = () => resolve(null); img.src = url; }); } /***/ }), /***/ "./CredentialManager.js": /*!******************************!*\ !*** ./CredentialManager.js ***! \******************************/ /*! exports provided: CredentialManager */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialManager", function() { return CredentialManager; }); /* harmony import */ var _CredentialHints_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./CredentialHints.js */ "./CredentialHints.js"); /*! * A CredentialManager for a Web Credential Mediator. * * Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved. */ /* global navigator */ class CredentialManager { constructor(url, injector) { if(!(url && typeof url === 'string')) { throw new TypeError('"url" must be a non-empty string.'); } this.hints = new _CredentialHints_js__WEBPACK_IMPORTED_MODULE_0__["CredentialHints"](url, injector); } /** * Requests that the user grant 'credentialhandler' permission to the current * origin. * * @return a Promise that resolves to the new PermissionState of the * permission (e.g. 'granted'/'denied'). */ static async requestPermission() { const status = await navigator.credentialsPolyfill.permissions.request( {name: 'credentialhandler'}); return status.state; } } /***/ }), /***/ "./CredentialRequestEvent.js": /*!***********************************!*\ !*** ./CredentialRequestEvent.js ***! \***********************************/ /*! exports provided: CredentialRequestEvent */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialRequestEvent", function() { return CredentialRequestEvent; }); /* harmony import */ var web_request_rpc__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! web-request-rpc */ "./node_modules/web-request-rpc/index.js"); /*! * A CredentialRequestEvent is emitted when a request has been made for * credentials. * * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. */ /* global Event */ // can't use "ExtendableEvent"; only accessible from Workers // TODO: may not be able to even extend `Event` here; could produce "incorrect" // core attributes class CredentialRequestEvent /*extends Event*/ { constructor({ credentialHandler, credentialRequestOrigin, credentialRequestOptions, hintKey }) { //super('credentialrequest'); this.type = 'credentialrequest'; this._credentialHandler = credentialHandler; this.credentialRequestOrigin = credentialRequestOrigin; this.credentialRequestOptions = credentialRequestOptions; this.hintKey = hintKey; } async openWindow(url) { // TODO: disallow more than one call // TODO: ensure `url` is to the same origin await this._credentialHandler.show(); const appWindow = new web_request_rpc__WEBPACK_IMPORTED_MODULE_0__["WebAppWindow"](url, { className: 'credential-handler' }); appWindow.ready(); appWindow.show(); // TODO: note that `appWindow.handle` is not a ServiceWorker // `WindowClient` polyfill... could be confusing here, should we // implement one to wrap it? -- there is, for example, a // `navigate` call on `WindowClient` that enforces same origin, would // need to attempt to add or approximate that return appWindow.handle; } respondWith(handlerResponse) { // TODO: throw exception if `_promise` is already set // TODO: validate handlerResponse this._promise = handlerResponse; } } /***/ }), /***/ "./CredentialStoreEvent.js": /*!*********************************!*\ !*** ./CredentialStoreEvent.js ***! \*********************************/ /*! exports provided: CredentialStoreEvent */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialStoreEvent", function() { return CredentialStoreEvent; }); /* harmony import */ var web_request_rpc__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! web-request-rpc */ "./node_modules/web-request-rpc/index.js"); /*! * A CredentialStoreEvent is emitted when a request has been made to * store a credential. * * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. */ /* global Event */ // can't use "ExtendableEvent"; only accessible from Workers // TODO: may not be able to even extend `Event` here; could produce "incorrect" // core attributes class CredentialStoreEvent /*extends Event*/ { constructor({ credentialHandler, credentialRequestOrigin, credential, hintKey }) { //super('credentialstore'); this.type = 'credentialstore'; this._credentialHandler = credentialHandler; this.credentialRequestOrigin = credentialRequestOrigin; this.credential = credential; this.hintKey = hintKey; } async openWindow(url) { // TODO: disallow more than one call // TODO: ensure `url` is to the same origin await this._credentialHandler.show(); const appWindow = new web_request_rpc__WEBPACK_IMPORTED_MODULE_0__["WebAppWindow"](url); appWindow.ready(); appWindow.show(); // TODO: note that `appWindow.handle` is not a ServiceWorker // `WindowClient` polyfill... could be confusing here, should we // implement one to wrap it? -- there is, for example, a // `navigate` call on `WindowClient` that enforces same origin, would // need to attempt to add or approximate that return appWindow.handle; } respondWith(handlerResponse) { // TODO: throw exception if `_promise` is already set // TODO: validate handlerResponse this._promise = handlerResponse; } } /***/ }), /***/ "./CredentialsContainer.js": /*!*********************************!*\ !*** ./CredentialsContainer.js ***! \*********************************/ /*! exports provided: CredentialsContainer */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CredentialsContainer", function() { return CredentialsContainer; }); /* harmony import */ var _WebCredential_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./WebCredential.js */ "./WebCredential.js"); /*! * Wrapper for native CredentialsContainer that uses remote Credential Mediator * for WebCredential-related operations. * * Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved. */ /* global navigator, DOMException */ // RPC timeouts, 0 = indefinite const CREDENTIAL_GET_TIMEOUT = 0; const CREDENTIAL_STORE_TIMEOUT = 0; class CredentialsContainer { constructor(injector) { this._nativeCredentialsContainer = { get: navigator.credentials && navigator.credentials.get && navigator.credentials.get.bind(navigator.credentials), store: navigator.credentials && navigator.credentials.store && navigator.credentials.store.bind(navigator.credentials), }; this._init = (async () => { this._remote = (await injector).get('credentialsContainer', { functions: [ {name: 'get', options: {timeout: CREDENTIAL_GET_TIMEOUT}}, {name: 'store', options: {timeout: CREDENTIAL_STORE_TIMEOUT}} ] }); })(); } async get(/*CredentialRequestOptions*/ options = {}) { if(options.web) { await this._init; const credential = await this._remote.get(options); if(!credential) { // no credential selected return null; } // TODO: validate credential return new _WebCredential_js__WEBPACK_IMPORTED_MODULE_0__["WebCredential"](credential.dataType, credential.data); } if(this._nativeCredentialsContainer.get) { return this._nativeCredentialsContainer.get(options); } throw new DOMException('Not implemented.', 'NotSupportedError'); } async store(credential) { if(credential instanceof _WebCredential_js__WEBPACK_IMPORTED_MODULE_0__["WebCredential"]) { await this._init; const result = await this._remote.store(credential); if(!result) { // nothing stored return null; } // TODO: validate result return new _WebCredential_js__WEBPACK_IMPORTED_MODULE_0__["WebCredential"](result.dataType, result.data); } if(this._nativeCredentialsContainer.store) { return this._nativeCredentialsContainer.store(credential); } throw new DOMException('Not implemented.', 'NotSupportedError'); } } /***/ }), /***/ "./PermissionManager.js": /*!******************************!*\ !*** ./PermissionManager.js ***! \******************************/ /*! exports provided: PermissionManager */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "PermissionManager", function() { return PermissionManager; }); /*! * Provides an API for working with permissions. * * Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved. */ // RPC timeouts, 0 = indefinite const PERMISSION_REQUEST_TIMEOUT = 0; class PermissionManager { constructor(injector) { this._init = (async () => { this._remote = (await injector).get('permissionManager', { functions: [ 'query', {name: 'request', options: {timeout: PERMISSION_REQUEST_TIMEOUT}}, 'revoke'] }); })(); } async query(permissionDesc) { await this._init; return await this._remote.query(permissionDesc); } async request(permissionDesc) { await this._init; return await this._remote.request(permissionDesc); } async revoke(permissionDesc) { await this._init; return await this._remote.revoke(permissionDesc); } } /***/ }), /***/ "./WebCredential.js": /*!**************************!*\ !*** ./WebCredential.js ***! \**************************/ /*! exports provided: WebCredential */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WebCredential", function() { return WebCredential; }); /*! * A WebCredential is a Credential that can be retrieved from or stored by a * "credential handler" that runs in a third party Web application. * * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. */ class WebCredential { constructor(dataType, data) { if(typeof dataType !== 'string') { throw new TypeError('"dataType" must be a string.'); } this.type = 'web'; this.dataType = dataType; this.data = data; } } /***/ }), /***/ "./index.js": /*!******************!*\ !*** ./index.js ***! \******************/ /*! exports provided: loadOnce, load */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "loadOnce", function() { return loadOnce; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "load", function() { return load; }); /* harmony import */ var web_request_rpc__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! web-request-rpc */ "./node_modules/web-request-rpc/index.js"); /* harmony import */ var _CredentialHandler_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./CredentialHandler.js */ "./CredentialHandler.js"); /* harmony import */ var _CredentialHandlers_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./CredentialHandlers.js */ "./CredentialHandlers.js"); /* harmony import */ var _CredentialManager_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./CredentialManager.js */ "./CredentialManager.js"); /* harmony import */ var _CredentialsContainer_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./CredentialsContainer.js */ "./CredentialsContainer.js"); /* harmony import */ var _PermissionManager_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./PermissionManager.js */ "./PermissionManager.js"); /* harmony import */ var _WebCredential_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./WebCredential.js */ "./WebCredential.js"); /*! * Credential Handler API Polyfill. * * Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved. */ /* global navigator, window */ const DEFAULT_MEDIATOR = 'https://authn.io/mediator' + '?origin=' + encodeURIComponent(window.location.origin); let loaded; async function loadOnce(mediatorUrl = DEFAULT_MEDIATOR) { if(loaded) { return loaded; } loaded = true; return load(mediatorUrl); } async function load(mediatorUrl = DEFAULT_MEDIATOR) { const appContext = new web_request_rpc__WEBPACK_IMPORTED_MODULE_0__["WebAppContext"](); const injector = appContext.createWindow(mediatorUrl, { className: 'credential-mediator', // 30 second timeout for loading the mediator timeout: 30000 }); // ensure backdrop is transparent by default const style = document.createElement('style'); style.appendChild(document.createTextNode( `dialog.web-app-window.credential-mediator > .web-app-window-backdrop { background-color: rgba(0, 0, 0, 0.25); }`)); document.body.appendChild(style); const polyfill = {}; // TODO: only expose certain APIs when appropriate polyfill.permissions = new _PermissionManager_js__WEBPACK_IMPORTED_MODULE_5__["PermissionManager"](injector); polyfill.CredentialHandlers = new _CredentialHandlers_js__WEBPACK_IMPORTED_MODULE_2__["CredentialHandlers"](injector); polyfill.CredentialHandler = _CredentialHandler_js__WEBPACK_IMPORTED_MODULE_1__["CredentialHandler"]; polyfill.CredentialManager = _CredentialManager_js__WEBPACK_IMPORTED_MODULE_3__["CredentialManager"]; polyfill.credentials = new _CredentialsContainer_js__WEBPACK_IMPORTED_MODULE_4__["CredentialsContainer"](injector); polyfill.WebCredential = _WebCredential_js__WEBPACK_IMPORTED_MODULE_6__["WebCredential"]; // expose polyfill navigator.credentialsPolyfill = polyfill; // polyfill if('credentials' in navigator) { navigator.credentials.get = polyfill.credentials.get.bind( polyfill.credentials); navigator.credentials.store = polyfill.credentials.store.bind( polyfill.credentials); } else { navigator.credentials = polyfill.credentials; } window.CredentialManager = _CredentialManager_js__WEBPACK_IMPORTED_MODULE_3__["CredentialManager"]; window.WebCredential = _WebCredential_js__WEBPACK_IMPORTED_MODULE_6__["WebCredential"]; return polyfill; } /***/ }), /***/ "./node_modules/web-request-rpc/Client.js": /*!************************************************!*\ !*** ./node_modules/web-request-rpc/Client.js ***! \************************************************/ /*! exports provided: Client */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Client", function() { return Client; }); /* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils.js */ "./node_modules/web-request-rpc/utils.js"); /*! * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. */ // 30 second default timeout const RPC_CLIENT_CALL_TIMEOUT = 30000; class Client { constructor() { this.origin = null; this._handle = null; this._listener = null; // all pending requests this._pending = new Map(); } /** * Connects to a Web Request RPC server. * * The Promise will resolve to an RPC injector that can be used to get or * define APIs to enable communication with the server. * * @param origin the origin to send messages to. * @param options the options to use: * [handle] a handle to the window (or a Promise that resolves to * a handle) to send messages to * (defaults to `window.parent || window.opener`). * * @return a Promise that resolves to an RPC injector once connected. */ async connect(origin, options) { if(this._listener) { throw new Error('Already connected.'); } options = options || {}; // TODO: validate `origin` and `options.handle` const self = this; self.origin = _utils_js__WEBPACK_IMPORTED_MODULE_0__["parseUrl"](origin).origin; self._handle = options.handle || window.parent || window.opener; const pending = self._pending; self._listener = _utils_js__WEBPACK_IMPORTED_MODULE_0__["createMessageListener"]({ origin: self.origin, handle: self._handle, expectRequest: false, listener: message => { // ignore messages that have no matching, pending request if(!pending.has(message.id)) { return; } // resolve or reject Promise associated with message const {resolve, reject, cancelTimeout} = pending.get(message.id); cancelTimeout(); if('result' in message) { return resolve(message.result); } reject(_utils_js__WEBPACK_IMPORTED_MODULE_0__["deserializeError"](message.error)); } }); window.addEventListener('message', self._listener); return new Injector(self); } /** * Performs a RPC by sending a message to the Web Request RPC server and * awaiting a response. * * @param qualifiedMethodName the fully-qualified name of the method to call. * @param parameters the parameters for the method. * @param options the options to use: * [timeout] a timeout, in milliseconds, for awaiting a response; * a non-positive timeout (<= 0) will cause an indefinite wait. * * @return a Promise that resolves to the result (or error) of the call. */ async send(qualifiedMethodName, parameters, { timeout = RPC_CLIENT_CALL_TIMEOUT }) { if(!this._listener) { throw new Error('RPC client not connected.'); } const self = this; const message = { jsonrpc: '2.0', id: _utils_js__WEBPACK_IMPORTED_MODULE_0__["uuidv4"](), method: qualifiedMethodName, params: parameters }; // HACK: we can't just `Promise.resolve(handle)` because Chrome has // a bug that throws an exception if the handle is cross domain if(_utils_js__WEBPACK_IMPORTED_MODULE_0__["isHandlePromise"](self._handle)) { const handle = await self._handle; handle.postMessage(message, self.origin); } else { self._handle.postMessage(message, self.origin); } // return Promise that will resolve once a response message has been // received or once a timeout occurs return new Promise((resolve, reject) => { const pending = self._pending; let cancelTimeout; if(timeout > 0) { const timeoutId = setTimeout(() => { pending.delete(message.id); reject(new Error('RPC call timed out.')); }, timeout); cancelTimeout = () => { pending.delete(message.id); clearTimeout(timeoutId); }; } else { cancelTimeout = () => { pending.delete(message.id); }; } pending.set(message.id, {resolve, reject, cancelTimeout}); }); } /** * Disconnects from the remote Web Request RPC server and closes down this * client. */ close() { if(this._listener) { window.removeEventListener('message', this._listener); this._handle = this.origin = this._listener = null; // reject all pending calls for(const value of this._pending.values()) { value.reject(new Error('RPC client closed.')); } this._pending = new Map(); } } } class Injector { constructor(client) { this.client = client; this._apis = new Map(); } /** * Defines a named API that will use an RPC client to implement its * functions. Each of these functions will be asynchronous and return a * Promise with the result from the RPC server. * * This function will return an interface with functions defined according * to those provided in the given `definition`. The `name` parameter can be * used to obtain this cached interface via `.get(name)`. * * @param name the name of the API. * @param definition the definition for the API, including: * functions: an array of function names (as strings) or objects * containing: {name: <functionName>, options: <rpcClientOptions>}. * * @return an interface with the functions provided via `definition` that * will make RPC calls to an RPC server to provide their * implementation. */ define(name, definition) { if(!(name && typeof name === 'string')) { throw new TypeError('`name` must be a non-empty string.'); } // TODO: support Web IDL as a definition format? if(!(definition && typeof definition === 'object' && Array.isArray(definition.functions))) { throw new TypeError( '`definition.function` must be an array of function names or ' + 'function definition objects to be defined.'); } const self = this; const api = {}; definition.functions.forEach(fn => { if(typeof fn === 'string') { fn = {name: fn, options: {}}; } api[fn.name] = async function() { return self.client.send( name + '.' + fn.name, [...arguments], fn.options); }; }); self._apis[name] = api; return api; } /** * Get a named API, defining it if necessary when a definition is provided. * * @param name the name of the API. * @param [definition] the definition for the API; if the API is already * defined, this definition is ignored. * * @return the interface. */ get(name, definition) { const api = this._apis[name]; if(!api) { if(definition) { return this.define(name, definition); } throw new Error(`API "${name}" has not been defined.`); } return this._apis[name]; } } /***/ }), /***/ "./node_modules/web-request-rpc/EventEmitter.js": /*!******************************************************!*\ !*** ./node_modules/web-request-rpc/EventEmitter.js ***! \******************************************************/ /*! exports provided: EventEmitter */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EventEmitter", function() { return EventEmitter; }); /*! * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. */ class EventEmitter { constructor({deserialize = e => e, waitUntil = async () => {}} = {}) { this._listeners = []; this._deserialize = deserialize; this._waitUntil = waitUntil; } async emit(event) { event = this._deserialize(event); (this._listeners[event.type] || []).forEach(l => l(event)); return this._waitUntil(event); } addEventListener(eventType, fn) { if(!this._listeners[eventType]) { this._listeners[eventType] = [fn]; } else { this._listeners[eventType].push(fn); } } removeEventListener(eventType, fn) { const listeners = this._listeners[eventType]; if(!listeners) { return; } const idx = listeners.indexOf(fn); if(idx !== -1) { listeners.splice(idx, 1); } } } /***/ }), /***/ "./node_modules/web-request-rpc/Server.js": /*!************************************************!*\ !*** ./node_modules/web-request-rpc/Server.js ***! \************************************************/ /*! exports provided: Server */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Server", function() { return Server; }); /* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils.js */ "./node_modules/web-request-rpc/utils.js"); /*! * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. */ class Server { constructor() { this.origin = null; this._handle = null; this._apis = new Map(); } /** * Provides an implementation for a named API. All functions in the given * API will be made callable via RPC clients connected to this server. * * @param name the name of the API. * @param api the API to add. */ define(name, api) { if(!(name && typeof name === 'string')) { throw new TypeError('`name` must be a non-empty string.'); } if(!(api && api !== 'object')) { throw new TypeError('`api` must be an object.'); } if(name in this._apis) { throw new Error(`The "${name}" API is already defined.`); } this._apis[name] = api; } /** * Listens for RPC messages from clients from a particular origin and * window handle and uses them to execute API calls based on predefined * APIs. * * If messages are not from the given origin or window handle, they are * ignored. If the messages refer to named APIs that have not been defined * then an error message is sent in response. These error messages can * be suppressed by using the `ignoreUnknownApi` option. * * If a message refers to an unknown method on a known named API, then an * error message is sent in response. * * @param origin the origin to listen for. * @param options the options to use: * [handle] a handle to the window (or a Promise that resolves to * a handle) to listen for messages from * (defaults to `window.parent || window.opener`). * [ignoreUnknownApi] `true` to ignore unknown API messages. */ async listen(origin, options) { if(this._listener) { throw new Error('Already listening.'); } options = options || {}; // TODO: validate `origin` and `options.handle` const self = this; self.origin = _utils_js__WEBPACK_IMPORTED_MODULE_0__["parseUrl"](origin).origin; self._handle = options.handle || window.parent || window.opener; const ignoreUnknownApi = (options.ignoreUnknownApi === 'true') || false; self._listener = _utils_js__WEBPACK_IMPORTED_MODULE_0__["createMessageListener"]({ origin: self.origin, handle: self._handle, expectRequest: true, listener: message => { const {name, method} = _utils_js__WEBPACK_IMPORTED_MODULE_0__["destructureMethodName"](message.method); const api = self._apis[name]; // do not allow calling "private" methods (starts with `_`) if(method && method.startsWith('_')) { return sendMethodNotFound(self._handle, self.origin, message); } // API not found but ignore flag is on if(!api && ignoreUnknownApi) { // API not registered, ignore the message rather than raise error return; } // no ignore flag and unknown API or unknown specific method if(!api || typeof api[method] !== 'function') { return sendMethodNotFound(self._handle, self.origin, message); } // API and specific function found const fn = api[method]; (async () => { const response = { jsonrpc: '2.0', id: message.id }; try { response.result = await fn.apply(api, message.params); } catch(e) { response.error = _utils_js__WEBPACK_IMPORTED_MODULE_0__["serializeError"](e); } // if server did not `close` while we waited for a response if(self._handle) { // HACK: we can't just `Promise.resolve(handle)` because Chrome has // a bug that throws an exception if the handle is cross domain if(_utils_js__WEBPACK_IMPORTED_MODULE_0__["isHandlePromise"](self._handle)) { self._handle.then(h => h.postMessage(response, self.origin)); } else { self._handle.postMessage(response, self.origin); } } })(); } }); window.addEventListener('message', self._listener); } close() { if(this._listener) { window.removeEventListener('message', this._listener); this._handle = this.origin = this._listener = null; } } } function sendMethodNotFound(handle, origin, message) { const response = { jsonrpc: '2.0', id: message.id, error: Object.assign({}, _utils_js__WEBPACK_IMPORTED_MODULE_0__["RPC_ERRORS"].MethodNotFound) }; // HACK: we can't just `Promise.resolve(handle)` because Chrome has // a bug that throws an exception if the handle is cross domain if(_utils_js__WEBPACK_IMPORTED_MODULE_0__["isHandlePromise"](handle)) { return handle.then(h => h.postMessage(response, origin)); } else { return handle.postMessage(response, origin); } } /***/ }), /***/ "./node_modules/web-request-rpc/WebApp.js": /*!************************************************!*\ !*** ./node_modules/web-request-rpc/WebApp.js ***! \************************************************/ /*! exports provided: WebApp */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WebApp", function() { return WebApp; }); /* harmony import */ var _Client_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Client.js */ "./node_modules/web-request-rpc/Client.js"); /* harmony import */ var _Server_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Server.js */ "./node_modules/web-request-rpc/Server.js"); /* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils.js */ "./node_modules/web-request-rpc/utils.js"); /*! * A WebApp is a remote application that runs in a WebAppContext. * * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. */ class WebApp { constructor(relyingOrigin) { // this is the origin that created the WebAppContext to run it in // TODO: better name? `contextOrigin`? this.relyingOrigin = Object(_utils_js__WEBPACK_IMPORTED_MODULE_2__["parseUrl"])(relyingOrigin).origin; this.client = null; this.injector = null; this.client = new _Client_js__WEBPACK_IMPORTED_MODULE_0__["Client"](); this.server = new _Server_js__WEBPACK_IMPORTED_MODULE_1__["Server"](); this._control = null; this._connected = false; } /** * Connects this WebApp to the relying origin that instantiated it. Once * connected, the WebApp can start servicing calls from that origin. * * @return a Promise that resolves to an injector for creating custom client * APIs once the connection is ready. */ async connect() { this.injector = await this.client.connect(this.relyingOrigin); this._connected = true; this._control = this.injector.define('core.control', { functions: ['ready', 'show', 'hide'] }); this.server.listen(this.relyingOrigin); return this.injector; } /** * Must be called after `connect` when this WebApp is ready to start * receiving calls from the remote end. */ async ready() { if(!this._connected) { throw new Error('WebApp not connected. Did you call ".connect()"?'); } await this._control.ready(); return this; } /** * Closes this WebApp's connection to the relying origin. */ close() { if(this._connected) { this.server.close(); this.client.close(); this._connected = false; } } /** * Shows the UI for this WebApp on the relying origin. */ async show() { if(!this._connected) { throw new Error( 'Cannot "show" yet; not connected. Did you call ".connect()"?'); } return this._control.show(); } /** * Hides the UI for this WebApp on the relying origin. */ async hide() { if(!this._connected) { throw new Error( 'Cannot "hide" yet; not connected. Did you call ".connect()?"'); } return this._control.hide(); } } /***/ }), /***/ "./node_modules/web-request-rpc/WebAppContext.js": /*!*******************************************************!*\ !*** ./node_modules/web-request-rpc/WebAppContext.js ***! \*******************************************************/ /*! exports provided: WebAppContext */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WebAppContext", function() { return WebAppContext; }); /* harmony import */ var _Client_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Client.js */ "./node_modules/web-request-rpc/Client.js"); /* harmony import */ var _Server_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Server.js */ "./node_modules/web-request-rpc/Server.js"); /* harmony import */ var _WebAppWindow_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./WebAppWindow.js */ "./node_modules/web-request-rpc/WebAppWindow.js"); /* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils.js */ "./node_modules/web-request-rpc/utils.js"); /*! * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. */ // 10 seconds const WEB_APP_CONTEXT_LOAD_TIMEOUT = 10000; class WebAppContext { constructor() { this