oidc-lib
Version:
A library for creating OIDC Service Providers
1,648 lines (1,415 loc) • 46.8 kB
JavaScript
(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 + ' </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 ';
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]);