UNPKG

oidc-lib

Version:

A library for creating OIDC Service Providers

949 lines (826 loc) 25.3 kB
var startupInvoked = false; var mainReadyToStart = false; pk.util.log_debug('[Main] Registering Service Worker'); navigator.serviceWorker.register('/wallet/service-worker.js', {scope: '/wallet/'}) .then(reg => { // reg is a serviceWorkerRegistration object pk.util.log_debug('[Main] Service Worker Registration Succeeded'); // Handler for messages coming from the service worker if (!startupInvoked && mainReadyToStart){ // pk.util.log_debug("Beginning Startup with delayed Registration"); // beginStartup(); pk.util.log_debug("NOT beginning Startup due to delayed Registration"); } }, err => { pk.util.log_debug('[Main] Service Worker Registration failed', err); }); navigator.serviceWorker.addEventListener('message', function(event){ var action = event.data.action; switch(action){ case 'install': if (startupInvoked){ pk.util.log_debug('[Main] Ignoring INSTALL message from ServiceWorker because startup begun.'); } else{ pk.util.log_debug('[Main] Received INSTALL message from Service Worker and invoking startup.') beginStartup(); } break; case 'managerNotification': case 'masterNotification': var retVal = masterNotification(event.data); break; } }); function send_message_to_serviceWorker(msg){ var safeMsg = pk.base64url.encode(JSON.stringify(msg)); return new Promise(function(resolve, reject){ if (navigator.serviceWorker.controller === null){ reject('serviceworker-not-ready'); } else{ // Create a Message Channel var msg_chan = new MessageChannel(); // Handler for recieving message reply from service worker msg_chan.port1.onmessage = function(event){ if(event.data.error){ reject(event.data.error); }else{ resolve(JSON.parse(pk.base64url.decode(event.data))); } }; // Send message to service worker along with port for reply navigator.serviceWorker.controller.postMessage(safeMsg, [msg_chan.port2]); } }); } // external packagefs paintLoadScreen(); var frameworkModules = { key_management: pk.key_management, did_management: pk.did_management, sts: pk.sts, token: pk.token, pmanager: pk.pmanager, pexchange: pk.pexchange, serialize64: pk.serialize64, claimer_crypto: pk.claimer_crypto, simple_crypto: pk.simple_crypto } var dbs = {}; var dbScaffold = pk.util.createDbScaffold(); var jsonForm = require('../wallet/jsonForm'); var featureModules = { 'wallet': { code: require('../wallet/wallet'), response_types: pk.util.config.content_modules['wallet']['responseTypes'], resources: { scope_claim_map: require('../wallet/data/scope_claim_map_auto') } }, 'sts': { resources: { scope_claim_map: require('../wallet/data/scope_claim_map_auto') } } }; for (var contentModuleName in pk.util.config.content_modules){ pk.app.registerCookie(pk.sts.cookie_identifier + contentModuleName); } if (pk.util.config.sts.httpsServerUrl !== undefined){ var httpsServerUrl = pk.util.url(pk.util.config.sts.httpsServerUrl); } var cookieKeys = ["abcdef123", "defhij234"]; var pinResolvers = {}; pk.feature_modules = featureModules; pk.dbs = dbs; pk.util.content_module_signing_key = pk.key_management.contentModuleSigningKey; pk.util.cookieKeys = cookieKeys; pk.util.httpsServerUrl = httpsServerUrl; pk.util.jsonForm = jsonForm; pk.util.masterNotification = masterNotification; pk.util.send_message_to_serviceWorker = send_message_to_serviceWorker; pk.util.swMessages = {}; pk.util.sync_info = new sync_info(); pk.util.sync_info.load_sync_time(); pk.util.submitPin = submitPin; // pk.util.get_c_key = get_c_key; function masterNotification(msg){ if (msg.action === 'managerNotification'){ elementName = 'manager_notifications' } else{ elementName = 'notification'; } var message = msg.message; var className = msg.className; var dismissible = msg.dismissible; var notificationsDiv = document.getElementById(elementName); if (!notificationsDiv){ notificationsDiv = document.getElementById('notification'); } if (notificationsDiv){ if (typeof message === "object"){ if (message.stack){ message = message.stack; } } else if (typeof message === "string"){ if (className === undefined){ className = 'alert-success'; } var notificationDiv = document.createElement('div'); notificationDiv.classList.add('alert', className); if (dismissible){ notificationDiv.classList.add('alert-dismissible'); } var html = ''; if (dismissible){ html += '<button type="button" class="close" data-dismiss="alert">&times;</button>'; } html += message; notificationDiv.innerHTML = html; notificationsDiv.appendChild(notificationDiv); return notificationDiv; } else if (message === false || message === undefined){ notificationsDiv.innerHTML = ''; } else if (message.parentElement){ message.parentElement.removeChild(message); } } } mainReadyToStart = true; if (navigator.serviceWorker.controller === null){ pk.util.log_debug('[Main] Controller not yet present.'); } else{ pk.util.log_debug('[Main] Controller exists.'); beginStartup(); } async function beginStartup(){ if (startupInvoked){ return; } else{ startupInvoked = true; } var potentialErrorMessage = ''; try { pk.util.operator_profile = await getOperatorProfile(); pk.app.entryPointUrlFunction = entryPointUrlFunction; if (!('indexedDB' in window)) { throw ('This browser doesn\'t support IndexedDB'); } potentialErrorMessage = "Error initializing databases"; dbResult = await initializeAllDataBases(dbScaffold); for (var dbInfo in pk.dbs){ if (dbInfo.contentModuleName === 'sts'){ continue; } else{ dbInfo.flockMembership = pk.dbs['sts'].flockMembership; } } for (var module_name in pk.feature_modules){ try { if (pk.feature_modules[module_name].code){ pk.feature_modules[module_name].code.registerEndpoints(pk); } } catch(err){ if (claimer_config.logging > 1){ console.error(chalk.red('Unable to register Endpoints for content module ' + module_name)); console.error(err); } else{ console.error(chalk.red('Unable to register Endpoints for content module ' + module_name)); console.error(chalk.red(err.message)); } } } for (var fmod in frameworkModules){ frameworkModules[fmod].registerEndpoints(pk); } console.log("checkIncognito"); potentialErrorMessage = "Error checking incognito"; var incognito = await checkIncognito(); console.log("queryPwaServerForStartupMode"); var startupMode = await queryPwaServerForStartupMode(); console.log("dbScaffold wallet"); if (dbScaffold['wallet']){ var c_key_buffer; try{ var w_key_string = await send_message_to_serviceWorker({action: 'get_w_key'}); if (w_key_string && w_key_string.length > 0){ var w_key = forge.util.createBuffer(pk.base64url.decode(w_key_string)); var dbPwaInfoArray = await pk.dbs['wallet'].provider.queryCollection (pk.dbs['wallet'], 'pwa', {}); var dbPwaInfo = dbPwaInfoArray[0]; var c_encryptedObj = JSON.parse(dbPwaInfo.c_encrypted); c_key_buffer = pk.pmanager.wallet_decrypt('AES-CBC', w_key, c_encryptedObj); } if (c_key_buffer){ for (var key in dbScaffold){ if (pk.util.operator_profile.wallet_config_group.encrypted === false){ pk.dbs[key].c_key = false; } else{ pk.dbs[key].c_key = c_key_buffer; console.log('c_key set in dbs[' + key + ']'); } } } else{ throw('Pwa unable to start up.') } } catch (err){ pk.util.log_error('queryPwaServerForStartupMode: Pwa unable to start up.', err); throw('c_key error - startup not possible.'); } } potentialErrorMessage = "Error initializing Personas"; await pk.ptools.initializePersonas(); var allPersonas = await pk.ptools.loadPersonas(incognito); potentialErrorMessage = "Error loading or creating keys"; await pk.key_management.loadOrCreateKeys(); console.log('invokeEntryPoint'); pk.app.invokeEntryPoint(); // if there have been changes to the database // publish encrypted to the service console.log('pexchange.exchange temporarily disabled'); // var result = await pk.pexchange.exchange(); // TODO: improve alg to receive notification when changes // are made and dispatch updates accordingly // var timerHandle = setInterval(exchangeTimer, 30000); // async function exchangeTimer() { // var result = await pk.pexchange.exchange(); // } var version = await send_message_to_serviceWorker({action: 'version'}); if (version){ pk.util.claimer_version = version; payload = { action: 'serviceUrl', serviceUrl: window.location.origin } var serviceUrlSet = send_message_to_serviceWorker(payload); } if (serviceUrlSet){ return(true); } } catch (err){ pk.util.log_debug('[Main] *******************************'); pk.util.log_debug(potentialErrorMessage + err); pk.util.log_debug('[Main] *******************************'); } } /* async function get_c_key(dbPwaInfo){ var w_key_string = await send_message_to_serviceWorker({action: 'get_w_key'}); var w_key = forge.util.createBuffer(pk.base64url.decode(w_key_string)); var auth_required = true; var c_key; while (auth_required){ var parameters = {}; try { if (w_key_string && w_key_string.length > 0){ c_key = pk.pmanager.wallet_decrypt('AES-CBC', w_key, dbPwaInfo.c_encrypted); } else{ var pinInfo = await getPin(last_error); var pin = pinInfo.pin; var pii = pinInfo.pii; if (pin.length < wallet_startup.pin_length){ error = 'Pin must be at least ' + wallet_startup.pin_length + ' characters long'; throw(error); } var md = forge.md.sha256.create(); md.update(pin + pii); var w_key = md.digest(); if (w_key !== dbPwaInfo.w_key){ error = 'Invalid Pin'; throw (error); } c_key = pk.pmanager.wallet_decrypt('AES-CBC', w_key, dbPwaInfo.c_encrypted); } } catch(err){ last_error = err; } auth_required = false; } return c_key; } */ async function queryPwaServerForStartupMode(){ var wallet_startup = pk.util.operator_profile.wallet_config_group.wallet_startup; var result = await pk.util.queryPwaApi('/wallet/wallet_id_request', {}); // if no connectivity if (!result){ try{ dbPwaInfo = await pk.dbs['wallet'].provider.queryCollection (pk.dbs['wallet'], 'pwa', {}); existing_dbPwaRecord = dbPwaInfo; } catch(err){ if (err.code === 404){ pk.util.log_error('queryPwaServerForStartupMode', err); throw('Pwa unable to start up.') } } } else{ var wallet_id = result.wallet_id; var dbPwaInfo; var existing_dbPwaRecord; var c_encrypted; try{ dbPwaInfo = await pk.dbs['wallet'].provider.getDocument(pk.dbs['wallet'], 'pwa', result.wallet_id); existing_dbPwaRecord = dbPwaInfo; } catch(err){ if (err.code !== 404){ pk.util.log_error('queryPwaServerForStartupMode', err); } } } var w_key_string = await send_message_to_serviceWorker({action: 'get_w_key'}); var auth_required = true; var last_error = ''; while (auth_required){ var parameters = {}; try { if (w_key_string && w_key_string.length > 0){ break; } var pinInfo = await getPin(last_error); var pin = pinInfo.pin; var pii = pinInfo.pii; if (pin.length < wallet_startup.pin_length){ error = 'Pin must be at least ' + wallet_startup.pin_length + ' characters long'; throw(error); } var md = forge.md.sha256.create(); md.update(pin + pii); var w_key = md.digest(); var serviceWorkerResponse = await send_message_to_serviceWorker( { action: 'set_w_key', data: pk.base64url.encode(w_key.bytes()) }); // w_id var w_id = await pk.simple_crypto.digestSha256( w_key.bytes() + wallet_id); if (dbPwaInfo){ parameters = { w_id: w_id } } else{ // c_key (content_key encrypted in c_encrypted) var c_key_buffer = forge.util.createBuffer(forge.random.getBytesSync(16)); // w_key // encrypt c_key using w_key var c_encryptedObj = pk.pmanager.wallet_encrypt('AES-CBC', w_key, c_key_buffer); var c_encrypted = JSON.stringify(c_encryptedObj); parameters = { w_id: w_id, c_encrypted: c_encrypted } } result = await pk.util.queryPwaApi('/wallet/register_request', parameters); switch (result.status){ case 'ERROR': pk.util.log_debug('queryPwaServerForStartupMode: ' + result.error); throw (result.error); case 'RESTORE': await process_restore(result); break; case 'OK': if (!dbPwaInfo){ dbPwaInfo = {}; } dbPwaInfo.id = wallet_id; dbPwaInfo.w_id = w_id; dbPwaInfo.pii = pii; if (c_encrypted){ dbPwaInfo.c_encrypted = c_encrypted; } if (dbPwaInfo.c_encrypted){ var c_encryptedObj = JSON.parse(dbPwaInfo.c_encrypted); c_key_buffer = pk.pmanager.wallet_decrypt('AES-CBC', w_key, c_encryptedObj); var serviceWorkerResponse = await send_message_to_serviceWorker( { action: 'set_c_key', data: pk.base64url.encode(c_key_buffer.bytes()) }); } if (isRecordChanged(dbPwaInfo, existing_dbPwaRecord)){ await pk.dbs['wallet'].provider.createOrUpdateDocument(pk.dbs['wallet'], 'pwa', dbPwaInfo); } // await pk.ptools.initializePersonas(); break; default: pk.util.log_error('queryPwaServerForStartupMode', 'unexpected status: ' + result.status); throw('Unexpected status: ' + result.status); } auth_required = false; } catch(err){ last_error = err; } } return result; } function isRecordChanged(new_record, old_record){ if (!old_record){ return true; } for (var key in new_record){ if (new_record[key] !== old_record[key]){ return true; } } for (var key in old_record){ if (old_record[key] !== new_record[key]){ return true; } } return false; } async function process_restore(api_result){ try{ if (!api_result.device_wallet){ alert('unexpected input in process_restore: ' + JSON.stringify(api_result)); return; } var device_wallet = api_result.device_wallet; if (device_wallet.version !== '1.0'){ alert('Could not restore wallet version: ' + device_wallet.version); return; } if (device_wallet.type !== 'fullSync'){ alert('Could not process wallet restore of type: ' + device_wallet.type); return; } for (var dbName in device_wallet.databases){ var dbInfos = device_wallet.databases[dbName]; for (var collectionName in dbInfos){ var collectionRecords = dbInfos[collectionName]; for (var i=0; i < collectionRecords.length; i++){ var record = collectionRecords[i]; var fixup = await pk.key_management.keyObjectImportKey(dbName, collectionName, record); var result = await pk.dbs[dbName].provider.createOrUpdateDocument(pk.dbs[dbName], collectionName, fixup ? fixup : record); } } } } catch(err){ alert('Error in process_restore: ' + err); } } async function getPin(last_error){ return new Promise((resolve, reject) => { var get_pin_b64 = pk.app.module_views.viewCollection['\\wallet\\views\\get_pin']; var get_pin_html = pk.base64url.decode(get_pin_b64); pinResolvers.resolve = resolve; pinResolvers.reject = reject; var notificationDiv = document.getElementById('render'); notificationDiv.innerHTML = get_pin_html; if (last_error){ var pin_alert_divEl = document.getElementById('pin_alert_div'); pin_alert_divEl.innerHTML = last_error; pk.util.setElementVisibility('pin_alert_div', true); } var getPinSaveEl = document.getElementById('get_pin_save'); getPinSaveEl.setAttribute('onclick', 'pk.util.submitPin()'); }); } function submitPin(){ var pinEl = document.getElementById('pin'); var pin_value = pinEl.value; var pinInfo = { pin: pin_value, pii: 'foobar' } var notificationDiv = document.getElementById('render'); notificationDiv.innerHTML = ''; pinResolvers.resolve(pinInfo); } async function initializeAllDataBases(dbScaffold){ var potentialErrorMessage = 'Error with initialize databases - '; try{ for (var cmName in dbScaffold){ var provider = dbScaffold[cmName]; if (provider !== undefined){ var dbInfo = await provider.initialize(pk, cmName, provider); pk.dbs[dbInfo.contentModuleName] = dbInfo; } } } catch (err){ throw(potentialErrorMessage + err); } } function calculateContentModuleScopes(content_scope_claim_maps){ var contentModuleScopes = {}; for (var module_name in content_scope_claim_maps){ if (content_scope_claim_maps[module_name] !== null){ var moduleScopes = []; var scope_claim_map = content_scope_claim_maps[module_name]; for (var key in scope_claim_map){ if (key.startsWith('fieldset_')){ var scope = scope_claim_map[key].scope; if (scope !== undefined){ moduleScopes.push(scope); } } } contentModuleScopes[module_name] = moduleScopes; } } return contentModuleScopes; } function entryPointUrlFunction(req){ pk.util.log_debug("**** REMOVE query.id_token from entryPointUrlFunction1! ****"); if (req.method === "GET"){ if (req.query.redirect_uri){ return '/wallet/auth'; } else if (req.query.req_cred){ return '/wallet/req_cred'; } else if (req.hash){ return('/wallet/process_credential_issuer_response'); } else if(req.query.code){ return('/wallet/process_code'); } else if (req.query.id_token){ return('/wallet/process_credential_issuer_response'); } else if (req.query.page){ return('/wallet/manager'); } else if (req.query.error){ return('/wallet/oauth_error'); } else if (req.query.w_id){ alert('wallet configuration complete'); return('/wallet/manager'); } else if (req.query.pickup_uri){ return('/wallet/pickup_uri'); } else if (req.query['entry_point']){ return('/wallet/entry_point'); } else{ var paramsPresent = false; for(var param in req.query){ paramsPresent = true; break; } if (paramsPresent){ alert("Invalid entry point: " + req.originalUrl); } else{ return('/wallet/manager'); } } } else{ alert('post entrypoints not implemented'); } } function storageSupported(){ return new Promise((resolve, reject) => { var fs = window.RequestFileSystem || window.webkitRequestFileSystem; if (!fs) { reject('check failed?'); } else { fs(window.TEMPORARY, 100, alert('true'), alert('false')); } }); } function checkIncognito(){ return new Promise((resolve, reject) => { var callback = function(result, param){ result(param); } var fs = window.RequestFileSystem || window.webkitRequestFileSystem; if (!fs) { // for now assume it is safari. Otherwise use reject('ENOENT'); var storage = window.sessionStorage; try { storage.setItem("safari_private", "false"); storage.removeItem("safari_private"); } catch (e) { if (e.code === DOMException.QUOTA_EXCEEDED_ERR && storage.length === 0) { resolve(true); } } resolve(false); } else { fs(window.TEMPORARY, 100, callback.bind(undefined, resolve, false), callback.bind(undefined, resolve, true) ); } }); } function paintLoadScreen(){ var html = '\ <div class="center-block" style="padding: 20%">\ <img style="height: 100%; width: 100%; object-fit: contain" src=\'/wallet/images/icons/{{splash}}\'/>\ </div>'; /* if (!window.location.search && (!pk || !pk.util || !pk.util.hubInstance || pk.util.hubInstance.flockDid === FLOCKNOTPRESENT)){ var renderEl = document.getElementById('render'); renderEl.innerHTML = html; } */ var splash; if (window.location.search.startsWith('?req_cred=')){ splash = 'blue-setup.png'; } else if (!window.location.search){ splash = 'blue-512x512.png'; } if (splash){ html = html.replace("{{splash}}", splash); var renderEl = document.getElementById('render'); renderEl.innerHTML = html; } } function sync_info(){ const LASTSYNCMONIKER = 'hub-last-sync'; var _last_sync_time = 0; // get does not load, and set does a lazy save Object.defineProperty(this, "last_sync_time", { get: function() { return _last_sync_time; }, set: function(value) { _last_sync_time = value; localStorage.setItem(LASTSYNCMONIKER, value); }, enumerable: true }); // returns a promise Object.defineProperty(this, "remove_sync_time", { value: function() { return new Promise((resolve, reject) => { _last_sync_time = 0; localStorage.remove(LASTSYNCMONIKER) .then(result => { if (result){ resolve(); } }, err => { reject(err); }) }); } }); // returns a promise Object.defineProperty(this, "load_sync_time", { value: function() { return new Promise((resolve, reject) => { var result = localStorage.getItem(LASTSYNCMONIKER); _last_sync_time = result; resolve(result); }); } }); } function dataStore(){ var _ds_info = new pk.util.db_module('indexed_db')(); var _dbInfo = null; Object.defineProperty(this, "set", { value: function(name, value) { return new Promise((resolve, reject) => { var initPromise; if (_dbInfo === null){ initPromise = _ds_info.initialize_local_storage(pk, 'sts', _ds_info); } else{ initPromise = Promise.resolve(_dbInfo); } initPromise .then(dbInfo => { if (dbInfo){ if (!_dbInfo){ _dbInfo = dbInfo; } var payload = { id: name, value: value } return _ds_info.createOrUpdateDocument(dbInfo, 'local_storage', payload); } }, err => { reject(err); }) .then(result => { if (result){ resolve(result); } }, err => { reject(err); }) }) } }) Object.defineProperty(this, "get", { value: function(name, encoding) { return new Promise((resolve, reject) => { var initPromise; if (_dbInfo === null){ initPromise = _ds_info.initialize_local_storage(pk, 'sts', _ds_info); } else{ initPromise = Promise.resolve(_dbInfo); } initPromise .then(dbInfo => { if (dbInfo){ if (!_dbInfo){ _dbInfo = dbInfo; } return _ds_info.getDocument(dbInfo, 'local_storage', name); } }, err => { reject(err); }) .then(payload => { if (payload){ resolve(payload.value); } }, err => { if (err && err.code === 404){ resolve(undefined); } else{ reject(err); } }) }) } }) Object.defineProperty(this, "remove", { value: function(name, encoding) { return new Promise((resolve, reject) => { var initPromise; if (_dbInfo === null){ initPromise = _ds_info.initialize_local_storage(pk, 'sts', _ds_info); } else{ initPromise = Promise.resolve(_dbInfo); } initPromise .then(dbInfo => { if (dbInfo){ if (!_dbInfo){ _dbInfo = dbInfo; } return _ds_info.deleteDocument(dbInfo, 'local_storage', name); } }, err => { reject(err); }) .then(payload => { if (payload){ resolve(payload); } }, err => { reject(err); }) }) } }) } async function getOperatorProfile(){ var profile_url = window.location.origin + '/wallet/operator_profile.json'; var options = { url: profile_url, method: 'GET', headers: [ { name: 'Accept', value: 'application/json' } ] }; var profile_content = {}; try{ var contentString = await pk.util.jsonHttpData(options); profile_content = JSON.parse(contentString); } catch{ } return profile_content; }