UNPKG

oidc-lib

Version:

A library for creating OIDC Service Providers

1,648 lines (1,415 loc) 46.8 kB
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ module.exports = { "email": { "input": "fieldset", "content": { "email": { "label": "Email address", "input": "email" }, "email_verified": { "label": "Verified", "input": "radio", "values": [ "true", "false" ], "default": "false", "editable": false } } }, "phone": { "input": "fieldset", "content": { "phone_number": { "label": "Phone", "input": "text" }, "phone_number_verified": { "input": "radio", "label": "Verified", "values": [ "true", "false" ], "default": "false", "editable": false } } }, "address": { "input": "fieldset", "legend": "Postal Address", "object": "address", "content": { "street_address": { "label": "Address", "input": "text" }, "street_address_1": { "label": "Apt or Unit", "input": "text" }, "locality": { "label": "City", "input": "text" }, "region": { "label": "State", "input": "text" }, "postal_code": { "label": "Zip", "input": "text" }, "country": { "label": "Country", "input": "text" }, "address": { "input": "object", "note": "contains all claims with object of \"address\"" } } }, "profile": { "input": "fieldset", "legend": "Profile", "content": { "name": { "label": "Name", "input": "text" }, "given_name": { "label": "First Name", "input": "text" }, "family_name": { "label": "Family Name", "input": "text" }, "middle_name": { "label": "Middle Name", "input": "text" }, "nickname": { "label": "Nickname", "input": "text" }, "preferred_username": { "label": "Username", "input": "text" }, "profile": { "label": "Profile", "input": "text" }, "picture": { "label": "Picture", "input": "photo" }, "website": { "label": "Web Site", "input": "text" }, "gender": { "label": "Gender", "input": "radio", "values": [ "male", "female", "alternative" ], "default": "female" }, "birthdate": { "label": "Date of Birth", "input": "text", "template": "YYYY-MM-DD" }, "zoneinfo": { "label": "Zone Info", "input": "text" }, "locale": { "label": "Locale", "input": "text" }, "updated_at": { "input": "text", "format": "date" } } } } },{}],2:[function(require,module,exports){ module.exports = { loadFormValues: loadFormValues, formToJson: formToJson, createInputDiv: createInputDiv, displayError: displayError, clearError: clearError, postJsonData: postJsonData } function loadFormValues(formTemplate, formValues){ clearError(); for (var parameter in formTemplate){ var definition = formTemplate[parameter]; var value = ''; var id = ''; var el; if (formValues[parameter] === undefined){ if (definition.default === 'suppress!'){ continue; } if (definition.default !== undefined){ value = definition.default; } } else{ value = formValues[parameter]; } if (value === undefined){ continue; } switch (definition.input){ case 'text': // text may be a simple text placed into a text box // or an array of text placed into a textarea // or an array of text placed into a text box... (e.g. scope) el = document.getElementById(parameter); if (typeof value === 'string'){ el.value = value; } else if (Array.isArray(value)){ if (value.length === undefined){ continue; } var valueSeparator = ' '; if (definition.type === 'array') valueSeparator = '\n'; var content = ''; var separator = ''; for (var i=0; i<value.length; i++){ var line = value[i].trim(); if (line.length > 0){ content += separator + line; separator = valueSeparator; } } el.value = content; } else { el.value = JSON.stringify(value); } break; case 'select': el = document.getElementById(parameter); el.value = value; break; case 'boolean': case 'radio': if (value){ id = parameter + '_' + value; el = document.getElementById(id); el.checked = true; } break; case 'checkboxes': if (value && value.length > 0){ for (var i=0; i < value.length; i++){ id = parameter + '_' + value[i]; el = document.getElementById(id); el.checked = true; } } break; case 'checkbox': if (value){ el = document.getElementById(parameter); el.checked = true; } break; case 'fieldset': loadFormValues(definition.content, formValues); break; case 'hidden': el = document.getElementById(parameter); el.value = value; default: break; } } } function flattenTemplates(result, template) { var definition; for (var parameter in template){ definition = template[parameter]; if (definition.input === 'fieldset'){ flattenTemplates(result, definition.content); } else{ result[parameter] = definition; } } } function formToJson(formId, formTemplate){ var flatTemplate = {}; flattenTemplates(flatTemplate, formTemplate); var formEl = document.getElementById(formId); var formInputs = formEl.getElementsByTagName('input'); var formTextAreas = formEl.getElementsByTagName('textarea'); var formSelects = formEl.getElementsByTagName('select'); var result = {}; for (var i=0; i < formInputs.length; i++){ var input_el = formInputs[i]; var name = input_el.name; var definition = flatTemplate[name]; if (definition === undefined) continue; switch (input_el.type){ case 'hidden': case 'text': case 'select': if (input_el.value.length !== 0) result[name] = input_el.value; break; case 'boolean': case 'radio': if (input_el.checked){ var prefix = name + '_'; value = input_el.id.substring(prefix.length); result[name] = value; } break; case 'checkbox': if (input_el.checked){ var prefix = name + '_'; if (input_el.id.startsWith(prefix)){ if (result[name] === undefined){ result[name] = []; } value = input_el.id.substring(prefix.length); result[name].push(value); } else{ result[name] = true; } } break; default: break; } } for (var i=0; i < formSelects.length; i++){ var select_el = formSelects[i]; var name = select_el.name; var definition = flatTemplate[name]; if (definition === undefined) continue; if (select_el.value.length !== 0) result[name] = select_el.value; } for (var i=0; i < formTextAreas.length; i++){ var textArea_el = formTextAreas[i]; var name = textArea_el.name; var definition = flatTemplate[name]; if (definition === undefined) continue; if (textArea_el.value.length !== 0){ if (definition.type === 'array'){ var valueArray = []; var text = textArea_el.value; var textArray = text.split('\n'); for (var j=0; j < textArray.length; j++){ if (textArray[j].length === 0) continue; valueArray.push(textArray[j]); } result[name] = valueArray; } else{ result[name] = textArea_el.value; } } } return result; } function createInputDiv(formDivId, formTemplate, formValues, scopes){ form_html = generateInputGroupingHtml(formTemplate, formValues); var form_el = document.getElementById(formDivId); form_el.innerHTML = form_html; } function generateInputGroupingHtml(formTemplate, formValues, scopes){ var label, formGroup, panel, value, id; var grouping_html = ''; for (var parameter in formTemplate){ var param_html = ''; var definition = formTemplate[parameter]; if (formValues[parameter] === undefined){ if (definition.default === 'suppress!'){ continue; } } switch (definition.input){ case 'text': case 'email': formGroup = createFormGroup(definition, parameter); if (definition.type === undefined || definition.type === 'string'){ param_html = formGroup + '<input type="text" id="' + parameter + '" name="' + parameter + '" class="form-control"/>\r\n'; } else{ param_html = formGroup + '<textarea rows="2" cols="60" wrap="hard" id="' + parameter + '" name="' + parameter + '" style="height: auto;"></textarea>\r\n'; } param_html += closeFormGroup(formGroup); break; case 'radio': checkValuesExist(parameter, definition); var divId = 'radio_' + parameter; formGroup = createFormGroup(definition, divId); param_html = formGroup + '<div id="' + divId + '">'; for (var i=0; i < definition.values.length; i++){ value = definition.values[i]; id = parameter + '_' + value; param_html += '<label class="radio-inline"><input type="radio" name="' + parameter + '" id="' + id + '" />' + value + '</label>'; } param_html += '</div>' + closeFormGroup(formGroup); break; case 'select': checkValuesExist(parameter, definition); label = createLabel(definition, parameter); param_html = label + '<select id="' + parameter + '" name="' + parameter + '" >\r\n'; for (var i=0; i < definition.values.length; i++){ value = definition.values[i]; param_html += '<option value="' + value + '">' + value + '</option>\r\n'; } param_html += "</select>\r\n"; break; // <div class="form-group form-check"> // <label class="form-check-label"> // <input class="form-check-input" type="checkbox"> Remember me // </label> // </div> case 'checkboxes': checkValuesExist(parameter, definition); panel = createPanelWithHeading(definition, parameter); param_html = panel + '<div class="form-group form-check">\r\n'; for (var i=0; i < definition.values.length; i++){ value = definition.values[i]; id = parameter + '_' + value; param_html += '<label class="form-check-label"><input class="form-check-input" id="' + id + '" name="' + parameter + '" type="checkbox">' + value + '</label>\r\n'; } param_html += closePanel(panel); break; case 'checkbox': label = createLabel(definition, parameter); param_html = '<label class="form-check-label"><input class="form-check-input" id="' + parameter + '" name="' + parameter + '" type="checkbox">' + label + '</label>\r\n'; break; case 'boolean': panel = createPanelWithHeading(definition, parameter); var bool_values = ['true', 'false']; for (var i=0; i < bool_values.length; i++){ value = bool_values[i]; id = parameter + '_' + value; param_html += '<input type="radio" name="' + parameter + '" id="' + id + '" />\r\n' + '<label for="' + id + '" class="inline">' + value + '&nbsp;&nbsp;</label>\r\n'; } param_html += closePanel(panel); break; case 'fieldset': if (scopes !== undefined && definition.scope !== undefined){ if (scopes.indexOf(definition.scope) < 0){ param_html = ''; break; } } param_html = generateInputGroupingHtml(definition.content, formValues); if (param_html.length > 0){ if (definition.legend !== undefined){ panel = createPanelWithHeading(definition, parameter); param_html = panel + param_html + closePanel(panel); } } break; case 'button': label = 'Submit'; if (definition.label !== undefined){ label = definition.label; } param_html = '<div class="col_3"><input type="button" value="' + label + '" onclick="' + definition.method + '" /></div>\r\n'; break; case 'hidden': param_html = '<input type="hidden" id="' + parameter + '" name="' + parameter + '"/>'; break; default: // throw 'undefined input type in reg_parameters: ' + definition.input break; } grouping_html += param_html; } return grouping_html; } function checkValuesExist(parameter, definition){ if (definition.values === undefined){ alert('formTemplate ' + definition.input + ' "values" for "' + parameter + '" is not defined'); throw 'error: values missing'; } } function createLabel(definition, parameter){ var text = parameter; if (definition.label !== undefined) text = definition.label; var label_class = ''; if (definition.label_class !== undefined) label_class = ' class="' + definition.label_class + '"'; return '<label for="' + parameter + '"' + label_class + '>' + text + '</label>\r\n'; } function createPanelWithHeading(definition, parameter){ var text = ''; if (definition.legend !== undefined) text = '<div class="panel panel-default">\r\n<div class="panel-heading">' + definition.legend + '</div>\r\n<div class="panel-body">'; return text; } function closePanel(panel){ var text = ''; if (panel !== '') text = '</div>\r\n</div>\r\n'; return text; } function createFormGroup(definition, parameter){ var label = createLabel(definition, parameter); return '<div class="form-group">' + label; } function closeFormGroup(formGroup){ return '</div>' } function postJsonData(options, jsonData, callback){ var xhr = new XMLHttpRequest(); var url = options.url; xhr.open(options.method, options.url, true); if (options.headers !== undefined){ for (var i=0; i < options.headers.length; i++){ var header = options.headers[i]; xhr.setRequestHeader(header.name, header.value); } } xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { var json = JSON.parse(xhr.responseText); callback(json); } }; var data = JSON.stringify(jsonData); xhr.send(data); } function displayError(content){ var warning_text_el = document.getElementById("warning_text"); var warning_div_el = document.getElementById("warning_div"); if (warning_text_el !== undefined) warning_text_el.innerHTML = content; if (warning_div_el !== undefined) warning_div_el.style.visibility = "visible"; } function clearError(content){ var warning_text_el = document.getElementById("warning_text"); var warning_div_el = document.getElementById("warning_div"); if (warning_text_el !== undefined) warning_text_el.innerHTML = ''; if (warning_div_el !== undefined) warning_div_el.style.visibility = "hidden"; } function isset(param){ if (typeof param !== 'object'){ if (param === undefined || param === null || param.length === 0) return false; } return true; } },{}],3:[function(require,module,exports){ const VIEWPATH = '\\wallet\\views\\'; module.exports = { registerEndpoints: registerEndpoints, invokeAuthUserAgent: invokeAuthUserAgent, invokeConsentUserAgent: invokeConsentUserAgent, generateUserinfo: generateUserinfo, processVerifiedIdToken: processVerifiedIdToken, token_presentation_options: token_presentation_options, setConsentMode: setConsentMode, VIEWPATH: VIEWPATH } const moduleName = 'wallet'; var viewPath = null; var pk = null; // var userAccounts = null; var dbInfo = null; var token_presentation_values = { imposeFormPostResponseMode: false, vcFormat: 'verifiablePresentation' }; function registerEndpoints(pkInput) { pk = pkInput; viewPath = VIEWPATH; // userAccounts = require('./data/accounts'); // var claimsMgr = require("./claimsmgr"); // claimsMgr.registerEndpoints(pk, userAccounts, getAccountBySub); pk.app.post('/wallet/auth_useragent_response', function(req, res){ processAuthUserAgentResponse(pk, req, res); }); pk.app.post('/wallet/consent_useragent_response', function(req, res){ processConsentUserAgentResponse(pk, req, res); }); pk.app.post('/wallet/process_ajax_request', function(req, res){ processAjaxRequest(pk, req, res); }); pk.app.get('/wallet/manager', function(req, res){ pk.pmanager.manager(pk, req, res); }); pk.app.get('/wallet/oauth_error', function(req, res){ pk.pmanager.oauth_error(pk, req, res); }); pk.app.get('/wallet/pickup_uri', function(req, res){ process_pickup_uri(pk, req, res); }); pk.app.get('/wallet/entry_point', function(req, res){ return process_wallet_entry_point(pk, req, res); }); pk.app.get('/wallet/oauth_error', function(req, res){ pk.pmanager.oauth_error(pk, req, res); }); // IMPORTANT // Additional 'wallet' endpoints are defined in // walletManager.js } function invokeAuthUserAgent(req, res, params, encoded_sts_state_bundle) { pk.util.log_debug('--- WALLET: INVOKE AUTH USERAGENT ---'); var content_module_state = {}; pk.util.log_debug("WARNING: login_hint has been disabled..."); /* var validLoginHint = false; if (params.login_hint){ var hint_persona = pk.ptools.getPersona('id', params.login_hint); if (hint_persona){ content_module_state.sub = hint_persona.id; validLoginHint = true; } } if (!validLoginHint){ content_module_state.sub = natural.id; } */ pk.sts.applyAuthResponse(res, encoded_sts_state_bundle, content_module_state); return; } /* placeholder if authUserAgentValidation required function processAuthUserAgentResponse (pk, req, res){ pk.sts.applyAuthResponse(res, req.body.encoded_sts_state_bundle, content_module_state); } */ function processVerifiedIdToken(res, verified_id_token, encoded_sts_state_bundle){ pk.util.log_debug('--- WALLET: PROCESS VERIFIED ID TOKEN ---'); var account = getAccountBySub(verified_id_token.sub); if (account === false){ return; } content_module_state = {}; content_module_state.sub = verified_id_token.sub; pk.sts.sendAuthResponse(res, encoded_sts_state_bundle, content_module_state); } async function updateConsentedToken(res, scopeInfo, encoded_sts_state_bundle, content_module_state){ var consentedPersona; var config = pk.ptools.getPersona('natural').data.options; if (!content_module_state.explicitConsent && config.share_consented_claims && content_module_state.consentInfo && content_module_state.consentInfo.idTokenContent && pk.sts.isScopeConsented(content_module_state)){ var id = content_module_state.consentInfo.credentialIssuerId; consentedPersona = pk.ptools.getPersona('id', id); } if (!consentedPersona){ return false; } var claims = content_module_state.consentInfo.idTokenContent; content_module_state.consentInfo = scopeInfo; content_module_state.consentInfo.currentPersona = content_module_state.sub; var requestOptions = { sub: content_module_state.sub } var distributedClaims = await pk.token.retrieveVerifiableCredential(consentedPersona, requestOptions); if (consentedPersona.vc_constants && distributedClaims){ var vcFormat = await pk.feature_modules['wallet'].code.token_presentation_options({ option: 'vcFormat' }); if (vcFormat === 'verifiablePresentation'){ var vp = { '@options': consentedPersona.vc_constants['@options'], type: [ 'VerifiablePresentation'], verifiableCredential: [ distributedClaims.JWT ] } claims['vp'] = vp; } else { claims._claim_names = {}; claims._claim_sources = {}; var effective_class = consentedPersona.vc_constants.type[consentedPersona.vc_constants.type.length - 1]; claims._claim_names[effective_class] = 'vc1'; claims._claim_sources['vc1'] = distributedClaims; } } content_module_state.scope_claim_map = consentedPersona.scope_claim_map; for (var i=0; i<consentedPersona.scopes_and_creds.length; i++){ var scred = consentedPersona.scopes_and_creds[i]; if (content_module_state.consentInfo.scopeArray.includes(scred)){ continue; } content_module_state.consentInfo.scopeArray.push(scred); } content_module_state.consentInfo.credentialIssuerId = consentedPersona.id; claims.sub = content_module_state.consentInfo.currentPersona; content_module_state.newIdTokenContent = claims; pk.sts.applyConsentResponse(res, encoded_sts_state_bundle, content_module_state); return (true); } async function invokeConsentUserAgent(res, scopeInfo, client_info, encoded_sts_state_bundle, content_module_state) { pk.util.log_debug('--- WALLET: INVOKE CONSENT USERAGENT ---'); // skip the UI experience if previous approval has been obtained // the user has configured the wallet for this and the RP is good with it if (await updateConsentedToken(res, scopeInfo, encoded_sts_state_bundle, content_module_state)){ return; } var client_info_class = 'clms_0'; var client_name_warning_class = 'clms_0'; // client_info will be set to null rather than undefined if startup was for wallet/applicaiton if (client_info !== null){ client_info_class = 'clms_1'; var client_uri = client_info.client_uri; if (client_uri === undefined){ client_uri = client_info.redirect_uri; } var client_name = client_info.client_name; if (client_name === undefined){ client_name = client_uri } var company_logo = client_info.company_logo; if (company_logo === undefined){ company_logo = "https://undefined.logo.uri"; // TODO: define default } var client_name_warning = false; var redirect_host = pk.util.url(client_info.redirect_uri); var client_host; if (client_info.client_uri !== undefined){ client_host = pk.util.url(client_info.client_uri); } if (client_host === undefined || client_host.hostname !== redirect_host.hostname){ client_uri = client_info.redirect_uri; client_name_warning = true; } } var scopeInfoString = JSON.stringify(scopeInfo); var scopeInfo64 = pk.base64url.encode(scopeInfoString); var previousConsentInfo = content_module_state.consentInfo; if (previousConsentInfo === undefined){ previousConsentInfo = {}; } var consentInfoString = JSON.stringify(previousConsentInfo); var consentInfo64 = pk.base64url.encode(consentInfoString); var natural = pk.ptools.getPersona('kind', 'natural'); var options = natural.data.options; var identifier_class = options.startup_identifier ? 'clms_1' : 'clms_0'; var pin_class = options.startup_pin ? 'clms_1' : 'clms_0'; if (client_name_warning){ client_name_warning_class = 'clms_1'; } // var cardset_blurb = 'Create and use persona cards to control what kind of data you share with different websites &nbsp;'; var cardset_blurb = 'Select an ID'; var contentModuleState64 = pk.base64url.encode(JSON.stringify(content_module_state)); res.render(viewPath + 'get_consent', { title: 'Getting Consent', client_name: client_name, client_uri: client_uri, client_redirect_uri: client_info.redirect_uri, cardset_blurb: cardset_blurb, company_logo: company_logo, danger: client_name_warning_class, client_info_class: client_info_class, identifier_class: identifier_class, pin_class: pin_class, scope_info: scopeInfo64, consent_info: consentInfo64, content_module_state: contentModuleState64, encoded_sts_state_bundle: encoded_sts_state_bundle }); } function setConsentMode(mode) { if (mode){ var cmsString = document.getElementById("content_module_state").value; var content_module_state = JSON.parse(pk.base64url.decode(cmsString)); if (content_module_state.login_hint){ var login_persona = pk.ptools.getPersona("id", content_module_state.login_hint); if (login_persona){ if (login_persona.kind === 'credential'){ pk.ptools.output_card(true); return; } } } } pk.util.setElementVisibility('new_consent', !mode); pk.util.setElementVisibility('existing_consent', mode); } async function processConsentUserAgentResponse (pk, req, res){ pk.util.log_debug('--- WALLET: PROCESS CONSENT USER AGENT RESPONSE ---'); var params = req.body; pk.util.log_detail('params', params); // TODO: check with server and ensure encryption var natural = pk.ptools.getPersona('kind', 'natural'); var options = natural.data.options; if (options.startup_pin){ if (options.startup_pin !== params.holder_credential){ pk.pmanager.managerNotification('Invalid Wallet PIN', 'alert-danger', true); return; } } var content_module_state = JSON.parse(pk.base64url.decode(params.content_module_state)); if (params.error){ content_module_state.error = params.error; } else{ content_module_state.scope_claim_map = params.scope_claim_map; var consentInfoString = pk.base64url.decode(params.scope_info); var consentInfo = JSON.parse(consentInfoString); // add creds to consent scopeArray since creds behave like scopes for (var i=0; i<params.scopes_and_creds.length; i++){ var scred = params.scopes_and_creds[i]; if (consentInfo.scopeArray.includes(scred)){ continue; } consentInfo.scopeArray.push(scred); } if (params.accepted === false){ var index = consentInfo.scopeArray.indexOf('openid'); if (index > -1) { consentInfo.scopeArray.splice(index, 1); } } if (params.currentPersona !== undefined){ consentInfo.currentPersona = params.currentPersona; } if (params.credentialIssuerId !== undefined){ consentInfo.credentialIssuerId = params.credentialIssuerId; } // TODO: make it possible to use the same thum that will be used in the final token // with persona keys, that could be the thumbprint of the persona key. // with pairwise keys, it is the thumbprint of the pairwise key params.claims.sub = consentInfo.currentPersona; content_module_state.newIdTokenContent = params.claims; content_module_state.consentInfo = consentInfo; } pk.sts.applyConsentResponse(res, params.encoded_sts_state_bundle, content_module_state); } function processAjaxRequest (pk, req, res){ pk.util.log_debug('--- WALLET: PROCESS AJAX REQUEST ---'); var params = req.body; pk.util.log_detail('params', params); if (validateStartupCredentials(res, params.startup_identifier, params.startup_pin) !== true){ return; } switch (params.op){ case 'check_credentials': sendFauxAjaxSubmit(res); break; default: sendFauxAjaxError(res, 'unknown_op', params.op); } } function generateUserinfo(res, params, consentInfo, accessTokenContent){ pk.util.log_debug('--- WALLET: GENERATE USERINFO ---'); pk.util.log_debug('Generating userInfo in wallet\r\n'); pk.util.log_detail('sub', accessTokenContent.sub); var account = getAccountBySub(accessTokenContent.sub); pk.sts.submitUserinfoResponse(res, moduleName, params, consentInfo, content_module_state.consentInfo.tokenContent); } /*********************************************************************************************/ // Simplistic stubs function validateStartupCredentials(res, startup_identifier, startup_pin){ var options = pk.ptools.getPersona('kind', 'natural').options; if (options.startup_identifier){ if (!startup_identifier){ return sendFauxAjaxError(res, 'invalid_authorization_parameters'); } startup_identifier = startup_identifier.toLowerCase(); if (options.startup_identifier.toLowerCase() !== startup_identifier){ return sendFauxAjaxError(res, 'incorrect startup id'); } } if (options.startup_pin){ if (!startup_pin){ return sendFauxAjaxError(res, 'invalid_authorization_parameters'); } if (options.startup_pin !== startup_pin){ pk.util.log_detail('startup id entered', startup_identifier); pk.util.log_detail('startup pin entered', startup_pin); return sendFauxAjaxError(res, 'invalid_pin'); } } return true; } function getAccountBySub(sub){ for(var key in userAccounts){ if (userAccounts[key].sub === sub){ return userAccounts[key]; } } } /*********************************************************************************************/ // utility function sendFauxAjaxError(res, error, error_description){ pk.util.log_detail('SENDING AJAX ERROR', error); var payload = { kind: 'error', detail: { error: error, error_description: error_description } }; res.json(payload); return false; } function sendFauxAjaxSubmit(res){ var payload = { kind: 'submit', }; res.json(payload); return true; } async function token_presentation_options(params){ switch (params.option){ case 'share_consented_claims': var config = pk.ptools.getPersona('natural').data.options; return config['share_consented_claims']; case 'tokenSigningKey': return await getPersonaSigningKey(params); break; case 'imposeFormPostResponseMode': case 'vcFormat': if (params.value){ token_presentation_values[params.option] = params.value; } else{ return token_presentation_values[params.option]; } break; } } async function getPersonaSigningKey(params){ try{ var sub = params.sub; if (sub === undefined){ throw('tokenSigningKey requested but no sub specified'); } // use the did index to look up the key with the correct did var keyAndStore = await pk.key_management.loadSingleKey({ dictionary: {"did": sub } }); return keyAndStore.keyObject; } catch(err){ throw(err); } } function process_pickup_uri(pk, req, res){ var pickup_uri = req.query.pickup_uri; if (!pickup_uri){ res.statusCode = 400; res.end(); return; } var credential_pickup_def = pk.util.operator_profile.wallet_config_group.credential_pickup; var matching_issuer = ''; if (credential_pickup_def){ for (var key in credential_pickup_def){ if (pickup_uri.startsWith(key)){ matching_issuer = credential_pickup_def[key]; break; } } } if (!matching_issuer){ res.statusCode = 400; res.end(); return; } // handle bug in OH rquest var pickup_uri_offset = req.originalUrl.indexOf('pickup_uri='); pickup_uri = req.originalUrl.substr(pickup_uri_offset + 11); pickup_uri = encodeURIComponent(pickup_uri); var wallet_url = pk.sts.selfIssuedIssuerIdentifier(); var claimsObj = { "id_token":{ "shc":{ value: pickup_uri } } } var claims = JSON.stringify(claimsObj); var url = wallet_url + '?req_cred=' + matching_issuer + '&claims=' + claims + '&next_step=' + wallet_url + '%3Fpage%3Dpersonas'; window.location = url; } async function process_wallet_entry_point(pk, req, res){ if (req.query.iss && req.query.login_hint){ return process_oidc_initiate_login(pk, req, res); } } async function process_oidc_initiate_login(pk, req, res){ try{ var error_message = 'Error getting oidc_initiate_login params'; var iss = req.query.iss; var login_hint = req.query.login_hint; var requiredProperties = { iss: iss } // check existience of credential var existing_credential; var registerResult; // var cred_persona = pk.ptools.getPersona('target', requiredProperties); if (!existing_credential){ var error_message = 'Error getting credential issuer metadata'; var metadataString = await pk.token.requestOPMetadata(iss); error_message = 'Unable to parse credential_issuer metadata'; var credential_issuer_metadata = JSON.parse(metadataString); var registration_endpoint = credential_issuer_metadata.registration_endpoint; if (!registration_endpoint){ error_message = 'Unable to retrieve credential_issuer registration endpoint'; throw("No registration endpoint"); } var postData = { redirect_uris: [ pk.sts.selfIssuedIssuerIdentifier() ] } var http_options = { url: registration_endpoint, method: 'POST', parseJsonResponse: true, headers: [ { name: 'Accept', value: 'application/json' }, { name: 'Content-type', value: 'application/json' } ], postData: postData } registerResult = await pk.util.jsonHttpData(http_options); registerResult.pkce = await pk.simple_crypto.createB64Code(48); } var persona_id = await pk.ptools.locate_or_add_credential_persona(iss, registerResult); if (!persona_id){ throw ('no persona_id located or added in create_persona_with_credential'); } // await request_credential(persona_id, login_hint, iss, next_step, claims) await request_credential(persona_id, login_hint, iss); } catch(err){ console.log('ERROR in oidc_initiate_login - ' + error_message, err); } } async function request_credential(persona_id, login_hint, issuer, next_step, claims) { try { error_message = 'Unable to connect to the credential_issuer'; var metadataString = await pk.token.requestOPMetadata(issuer); error_message = 'Unable to parse credential_issuer metadata'; var credential_issuer_metadata = JSON.parse(metadataString); error_message = 'Error getting or creating credential_issuer claims'; var credential_issuer_claims = await getOrCreateCredentialIssuerClaims(issuer, persona_id); if (credential_issuer_claims){ var endpoint = credential_issuer_metadata.authorization_endpoint; var serviceUrl = pk.sts.selfIssuedIssuerIdentifier(); var redirect_uri = serviceUrl; var nonce = await pk.simple_crypto.randomString(); var nonceInfo = { nonce: nonce, persona_id: persona_id } pk.nonceCache.set('cred_request_nonce_info', JSON.stringify(nonceInfo)); var scope = 'openid openid_credential'; var response_type = 'id_token token'; response_type = 'code'; var state_params = { tok_ept: credential_issuer_metadata.token_endpoint, cred_ept: credential_issuer_metadata.credential_endpoint, sub: persona_id } if (next_step){ state_params.next_step = next_step; } var state = pk.util.createParameterString(state_params).substring(1); var persona = pk.ptools.getPersona('id', persona_id); var parameters = { client_id: persona.client_id, redirect_uri: redirect_uri, response_type: response_type, nonce: nonce, state: state, scope: scope, login_hint: login_hint, code_challenge: await pk.simple_crypto.digestSha256(persona.data.pkce), code_challenge_method: 'S256' }; if (claims){ parameters.claims = claims; } var parameterString = pk.util.createParameterString(parameters); window.location = endpoint + parameterString; } else{ alert('Error populating credential_issuer object'); throw ("no credential_issuer claims in request_credential"); } } catch (err){ var alert_message = error_message + err; if (error_message){ pk.pmanager.managerNotification(error_message, 'alert-warning', true); } else{ pk.pmanager.managerNotification(removeNotification); } } async function getOrCreateCredentialIssuerClaims(issuer, persona_id){ try { var credential_issuer_claims_id = persona_id; var credential_issuer_claims = await pk.dbs['wallet'].provider.getDocument(pk.dbs['wallet'], 'credential_issuer_claims', credential_issuer_claims_id); } catch (err){ // this doesn't exist yet - create it so the response from the // credential_issuer will be accepted var proposed_credential_issuer_claims = { issuer: issuer, id: persona_id }; credential_issuer_claims = await pk.dbs['wallet'].provider.createOrUpdateDocument(pk.dbs['wallet'], 'credential_issuer_claims', proposed_credential_issuer_claims); } return credential_issuer_claims; } } },{}],4:[function(require,module,exports){ var startupInvoked = false; var mainReadyToStart = false; // paintLoadScreen(); const clientlib = window.claimerClientLib; var frameworkModules = { key_management: pk.key_management, sts: pk.sts, token: pk.token, pmanager: pk.pmanager, serialize64: pk.serialize64 } var dbScaffold = pk.util.createDbScaffold(); var jsonForm = require('../wallet/jsonForm'); var featureModules = { 'wallet': { code: require('../wallet/wallet') } }; var templatePaths = { './wallet/data/scope_claim_map': require('../wallet/data/scope_claim_map_auto') } var content_scope_claim_maps = { 'wallet': require('../wallet/data/scope_claim_map_auto') }; var contentModuleResponseTypes = {}; for (var contentModuleName in pk.util.config.content_modules){ pk.app.registerCookie(pk.sts.cookie_identifier + contentModuleName); var responseTypes = ["code", "id_token", "id_token token", "code id_token", "code token", "code id_token token"]; if (pk.util.config.content_modules[contentModuleName]['responseTypes'] !== undefined){ responseTypes = pk.util.config.content_modules[contentModuleName].responseTypes; } contentModuleResponseTypes[contentModuleName] = responseTypes; } pk.feature_modules = featureModules; pk.dbs = {}; pk.util.content_module_response_types = contentModuleResponseTypes; pk.util.content_module_signing_key = pk.key_management.contentModuleSigningKey; pk.util.jsonForm = jsonForm; 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 { 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; } } var content_scope_claim_maps = {}; for (var module_name in pk.feature_modules){ try { pk.feature_modules[module_name].code.registerEndpoints(pk); content_scope_claim_maps[module_name] = initializeSingleContentModuleConfig(module_name, 'scope_claim_map'); } 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)); } } } pk.util.content_scope_claim_maps = content_scope_claim_maps; for (var fmod in frameworkModules){ frameworkModules[fmod].registerEndpoints(pk); } // potentialErrorMessage = "Error checking incognito"; // var incognito = await checkIncognito(); potentialErrorMessage = "Error initializing Personas"; var allPersonas = await pk.ptools.initializePersonas(false); potentialErrorMessage = "Error loading or creating keys"; await pk.key_management.loadOrCreateKeys(); // on completion call the function transformed from the pathname // by replacing slashes and periods with underscores // -- for example /wallet/test.html results in a call to // the global function _wallet_test_html var pathfunc = window.location.pathname.replace(/\/|\.|\-/g, '_'); potentialErrorMessage = "Error attempting to invoke startup function '" + pathfunc + "' in your script. "; this[pathfunc](); return true; } catch (err){ console.log('[Main] *******************************'); console.log(potentialErrorMessage + err); console.log('[Main] *******************************'); } } function initializeAllDataBases(dbScaffold){ var flockMembership; var initPromises = []; return new Promise((resolve, reject) => { for (var cmName in dbScaffold){ var provider = dbScaffold[cmName]; if (provider !== undefined){ var promise = provider.initialize(pk, cmName, provider); initPromises.push(promise); } } Promise.all(initPromises) .then(data => { for (var i=0; i < data.length; i++){ var dbInfo = data[i]; pk.dbs[dbInfo.contentModuleName] = dbInfo; } resolve(true); }, err => { reject("error with initializeDatabase promises: " + 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 initializeSingleContentModuleConfig(module_name, kind){ var moduleExport = feature_modules[module_name].code; if (moduleExport['invokeConsentUserAgent'] === undefined){ return null; } var claims = []; var content; var templatePath = './claimer_content/' + module_name + '/data/' + kind; try{ if (typeof window === 'undefined'){ content = require(templatePath); } else{ content = templatePaths[templatePath]; } if (moduleExport['registerModuleScope'] !== undefined){ var scope = moduleExport.registerModuleScope(); for (var scopeKey in scope){ content[scopeKey] = scope[scopeKey]; } } } catch (err){ if (err.code === 'MODULE_CONFIG_NOT_FOUND'){ pk.util.log_debug("*** Warning: content module " + module_name + ' contains an error defining ' + kind + ' at ' + templatePath + '.js... '); } else { pk.util.log_debug("*** Warning: content module " + module_name + ' contains an error defining ' + kind + ' at ' + templatePath + '.js... '); } } return content; } 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>'; 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 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); }) }) } }) } */ },{"../wallet/data/scope_claim_map_auto":1,"../wallet/jsonForm":2,"../wallet/wallet":3}]},{},[4]);