oidc-lib
Version:
A library for creating OIDC Service Providers
462 lines (396 loc) • 15.2 kB
JavaScript
var pk = {};
module.exports = {
init_oidc_lib: init_oidc_lib,
start_server: start_server
}
require('./claimer_sts/util_functions').init(pk);
async function init_oidc_lib(feature_config, platform_config)
{
pk.util.WALLET_ENDPOINT = '/wallet/wallet.html';
pk.express = require('express');
pk.app = pk.express();
pk.path = require('path');
pk.app = pk.express();
pk.claimer_crypto = require('./claimer_sts/claimer_crypto');
pk.jose = require('node-jose');
pk.fetch = require('node-fetch');
pk.fs = require('fs');
pk.https = require('https');
pk.http = require('http');
pk.url_module = require('url');
pk.chalk = require('chalk');
pk.querystring = require('querystring');
pk.nodeCache = require( "node-cache" );
pk.base64url = require('base64url');
pk.base58 = require('base-58');
pk.request = require('request');
pk.cookie = require('cookie');
pk.serialize64 = require('./claimer_sts/serialize64');
pk.simple_crypto = require('./claimer_sts/simple_crypto');
pk.cors = require('cors');
pk.mqtt = require('mqtt');
//pk.oidc_rp = require('./oidc/rp/oidc_rp');
pk.issuer = require('./roles/issuer');
pk.client = require('./roles/client')
pk.forge = require('node-forge');
pk.zlib = require('zlib');
pk.JSONPath = require('JSONPath');
// feature configurations
pk.config_builder = require('./claimer_sts/config_builder');
pk.util.config = pk.config_builder.build(pk, feature_config);
if (pk.util.config.useAlternateConfig !== undefined){
pk.util.config = require(pk.util.config.useAlternateConfig);
}
// platform configuration
apply_platform_config(platform_config, pk.util.config);
// framework modules which require endpoint registration
pk.sts = require('./claimer_sts/claimer_sts');
pk.client_info = require('./claimer_sts/client_info');
pk.token = require('./claimer_sts/token_management');
pk.key_management = require('./claimer_sts/key_management');
pk.did_management = require('./claimer_sts/did_management')
var wallet_key_type_class = require('./claimer_sts/wallet_key_type');
pk.wallet_key_types = new wallet_key_type_class.wallet_key_type();
// these modules contain RegisterEndpoints
var frameworkModules = {
sts: pk.sts,
token: pk.token,
key_management: pk.key_management,
did_management: pk.did_management,
serialize64: pk.serialize64,
claimer_crypto: pk.claimer_crypto,
simple_crypto: pk.simple_crypto
}
pk.util.log_always('oidc-lib initializing at ' + Date());
pk.codeCache = new pk.nodeCache({stdTTL: pk.util.config.sts.codeTTL, checkperiod: 120 });
pk.nonceCache = new pk.nodeCache({stdTTL: pk.util.config.sts.nonceTTL, checkperiod: 120 });
// set up the featureModules...
pk.feature_modules = populateFeatureModules(feature_config);
var dbs = {};
var dbScaffold = pk.util.createDbScaffold();
pk.dbs = dbs;
pk.util.httpsListeners = readHttpsListeners();
pk.util.httpsServerUrls = {};
for (var moniker in pk.util.httpsListeners){
pk.util.httpsServerUrls[moniker] = pk.util.url(pk.util.httpsListeners[moniker]);
}
/*
if (pk.util.config.sts.httpsServerUrl !== undefined){
pk.util.httpsServerUrl = pk.util.url(pk.util.config.sts.httpsServerUrl);
}
*/
pk.util.cookieKeys = pk.util.config.sts.cookieKeys ? pk.util.config.sts.cookieKeys : ["abcdef123", "defhij234"];
// set up express
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const exphbs = require('express-handlebars');
pk.app.engine('handlebars', exphbs({defaultLayout: 'main'}));
pk.app.set('view engine', 'handlebars');
// Body Parser Middleware
pk.app.use(bodyParser.json({limit: "1mb"}));
pk.app.use(bodyParser.urlencoded({limit: "10mb", extended: false }));
pk.app.use(cookieParser());
/*
// attempting to move to wallet.js feature
pk.app.get('/wallet/wallet.html', function(req, res){
if (pk.feature_modules.wallet){
pk.feature_modules.wallet.code['processPwaStart'](req, res);
}
});
*/
// add static web site
if (!pk.fs.existsSync(pk.util.config.feature_web_path)){
throw ('apply_platform_config: feature_web_path MUST be a directory that exists in the filesystem. - ' + pk.util.config.feature_web_path + ' does not exist.');
}
pk.app.use(pk.express.static(pk.util.config.feature_web_path));
await beginStartup(dbScaffold, frameworkModules);
return pk;
}
function populateFeatureModules(feature_config){
var featureModules = {};
for (var module in pk.util.config.content_modules){
if (pk.util.config.content_modules[module].enabled !== false){
// load the js file that constitutes the module
try {
var resources = {};
for (var resource_name in feature_config[module].resources){
var resource_path = feature_config[module].resources[resource_name];
var content = pk.fs.readFileSync(resource_path, {encoding: 'utf8'});
if (resource_path.endsWith('.json')){
content = JSON.parse(content);
}
resources[resource_name] = content;
}
if (resources.scope_claim_map){
resources.scope_claim_map = updateScopeClaimMapIfRequired(module, resources.scope_claim_map);
}
var allResponseTypes = ["code", "id_token", "id_token token", "code id_token", "code token", "code id_token token"];
featureModules[module] = {
code: require(feature_config[module].function),
response_types: feature_config[module].responseTypes ?
feature_config[module].responseTypes : allResponseTypes,
resources: resources
}
pk.util.log_debug("Loaded feature module: " + module);
}
catch (err) {
var msg = 'feature module ' + module + ' was not found at ' + feature_config[module].function + '... ';
pk.util.log_error(msg, err);
}
}
}
// although sts is framework, not a feature, add it to feature_modules
// so resources can be defined symmetrically with features
var sts_resources = {};
var sts_resource_defs = [{ name: 'scope_claim_map', path: 'scope_claim_map.json' }];
for (sts_resource_def of sts_resource_defs){
var resource_path = __dirname + '/claimer_sts/data/' + sts_resource_def.path;
var content = pk.fs.readFileSync(resource_path, {encoding: 'utf8'});
if (resource_path.endsWith('.json')){
content = JSON.parse(content);
}
sts_resources[sts_resource_def.name] = content;
}
featureModules['sts'] = {
resources: sts_resources
}
return featureModules;
}
async function beginStartup(dbScaffold, frameworkModules){
try {
await initializeAllDataBases(dbScaffold);
// Register framework_modules
for (var fmod in frameworkModules){
frameworkModules[fmod].registerEndpoints(pk);
}
// Set up content_modules
// if client_api exists, it must be done first
// and requires none of the issuer scope handling
if (pk.feature_modules.client_api){
pk.util.log_always("///// Initializing the client_api /////")
pk.feature_modules.client_api.code.registerEndpoints(pk);
}
for (var module_name in pk.feature_modules){
if (module_name === 'client_api'){
continue;
}
try {
// faux feature module 'sts' and potentially other modules
// will not have registered code
if (pk.feature_modules[module_name].code){
pk.feature_modules[module_name].code.registerEndpoints(pk);
}
}
catch(err){
console.error('Unable to register Endpoints for content module ' + module_name);
console.error(err);
}
}
// Create or load all the keys
var keysLoaded = await pk.key_management.loadOrCreateKeys();
pk.util.log_debug('Ready to start server');
}
catch (err){
pk.util.log_error("beginStartup - fatal", err);
process.exit(1);
}
}
async function initializeAllDataBases(dbScaffold){
try{
for (var cmName in dbScaffold){
var provider = dbScaffold[cmName];
if (provider !== undefined){
pk.dbs[cmName] = await provider.initialize(pk, cmName, provider);
}
}
}
catch (err){
throw ("error with initializeDatabase promises: " + err);
};
}
// if the input_credential_type in the configuration has changed, scope_claim_map
// will be out of date. However it may have been customized, so we construct
// an entry for the new cred_type and leave the customization untouched
function updateScopeClaimMapIfRequired(module_name, scope_claim_map){
// the existing claim_map is passed in as a parameter
var module_config = pk.util.config.content_modules[module_name].config;
if (!module_config || !module_config.input_credential_type){
return scope_claim_map;
}
var credType = module_config.input_credential_type;
// if there was no claim map, null is passed in
if (!scope_claim_map){
scope_claim_map = {};
}
// if the current input_credential_type is not in the scope map
if (!scope_claim_map[credType]){
var shortCredType = credType;
var slashOffset = credType.lastIndexOf('/');
if (slashOffset >=0){
shortCredType = shortCredType.substr(slashOffset + 1);
}
var ancillaryClaims = module_config.ancillaryClaims;
var acArray = ancillaryClaims.split(/[\s]+/);
acArray.push(shortCredType);
var content = {};
for (var i=0; i < acArray.length; i++){
content[acArray[i]] = {
label: acArray[i],
input: "text"
};
}
scope_claim_map[credType] = {
input: "fieldset",
content: content
};
var content_directory = process.cwd() + '/' + module_name + '/data';
pk.util.mkDirectoriesInPath(content_directory);
pk.fs.writeFileSync(pk.path.join(content_directory, 'scope_claim_map.json'), JSON.stringify(scope_claim_map, null, ' '), 'utf8');
}
return scope_claim_map;
}
function readHttpsListeners(){
try{
var possibleError = '"listener_path" is undefined - set it in platform_config parameter of call to init_oidc_lib';
if (!pk.util.config.sts.listener_path){
throw (possibleError);
}
possibleError = '"listener_path" is defined as ' + pk.util.config.sts.listener_path + ' - but the file does not exist';
if (!pk.fs.existsSync(pk.util.config.sts.listener_path)){
throw (possibleError);
}
possibleError = '"listener_path" is defined as ' + pk.util.config.sts.listener_path + ' - but the file cannot be read';
var listenersString = pk.fs.readFileSync(pk.util.config.sts.listener_path);
possibleError = pk.util.config.sts.listener_path + ' cannot be parsed';
var listeners = JSON.parse(listenersString);
return listeners;
}
catch(err){
pk.util.log_error('Error reading Https Listeners: ' + possibleError);
process.exit(1);
}
}
function start_server(){
try{
// no longer supported in unix
if (!process.env.PATH.includes("/usr/bin")){
process.on('uncaughtException', (err) => {
shutdownPorts('UNCAUGHT EXCEPTION');
});
process.on('SIGINT', function(){
shutdownPorts('SIGINT');
process.exit(1);
});
process.on('SIGKILL', function(){
shutdownPorts('SIGKILL');
process.exit(1);
});
}
var credentialsPathPlusName = pk.util.config.sts.https_certificate_filename;
var credentials = {
key: pk.fs.readFileSync(credentialsPathPlusName + '.key'),
cert: pk.fs.readFileSync(credentialsPathPlusName + '.cer')
};
pk.util.httpsServers = {};
for (var moniker in pk.util.httpsServerUrls){
pk.util.httpsServers[moniker] = pk.https.createServer(credentials, pk.app);
pk.util.httpsServers[moniker].on('error', (e) => {
if (e.code === 'EADDRINUSE') {
pk.util.log_debug('Address in use, retrying...');
setTimeout(() => {
var thisServer = pk.util.httpsServers[moniker];
thisServer.close();
thisServer.listen(thisServer.port, '0.0.0.0');
}, 1000);
}
else {
pk.util.log_debug('Server listener error: ' + e.code);
pk.util.log_debug('Details', e);
}
});
// The following did not work on ubuntu - listens on '0.0.0.0'
// pk.util.httpsServers[moniker].listen(pk.util.httpsServerUrls[moniker].port, pk.util.httpsServerUrls[moniker].hostname);
pk.util.httpsServers[moniker].listen(pk.util.httpsServerUrls[moniker].port, '0.0.0.0');
pk.util.log_always('Https server for ' + moniker + ' started on ' + pk.util.httpsServerUrls[moniker].host);
}
}
catch(err){
pk.util.log_error('Error Starting Server', err);
process.exit(1);
}
}
function initializeModuleDataResource(module_name, kind){
try{
var templatePath;
if (module_name === 'sts'){
templatePath = __dirname + '/claimer_sts/data/' + kind + '.json';
}
else{
var moduleExport = pk.feature_modules[module_name].code;
if (moduleExport['invokeConsentUserAgent'] === undefined){
return null;
}
var content_directory = process.cwd() + '/' + module_name + '/data';
pk.util.mkDirectoriesInPath(content_directory);
templatePath = content_directory + '/' + kind + '.json';
// scope claim maps are not mandatory inf not needed (an example
// being when created by ancillary claims)
if (!pk.fs.existsSync(templatePath) && kind === 'scope_claim_map'){
return null;
}
}
var contentString = pk.fs.readFileSync(templatePath, {encoding: 'utf8'});
var content = JSON.parse(contentString);
}
catch (err){
if (err.code === 'MODULE_CONFIG_NOT_FOUND'){
pk.util.log_error('initializeModuleDataResource', 'module ' + module_name + ' contains an error defining ' + kind + ' at ' + templatePath + '.js... ');
}
else {
pk.util.log_error('initializeModuleDataResource', 'content module ' + module_name + ' contains an error defining ' + kind + ' at ' + templatePath + '.js... --> ' + err.stack);
}
}
return content;
}
function shutdownPorts(signalName){
pk.util.log_debug('++++++++++++++++++++ ' + signalName + ' ++++++++++++++++++++');
for (var moniker in pk.util.httpsServers){
if (pk.util.httpsServers[moniker] !== undefined){
pk.util.httpsServers[moniker].close();
pk.util.log_debug('httpsServer closed: ' + moniker);
}
else{
pk.util.log_debug('httpsServer not defined: ' + moniker);
}
}
if (pk.util.notificationServer !== undefined){
pk.util.notificationServer.close();
pk.util.log_debug('notificationServer closed');
}
else{
pk.util.log_debug('notificationServer not defined');
}
}
function apply_platform_config(platform_config, config){
if (!platform_config){
return;
}
if (platform_config.logging !== undefined){
pk.util.config.logging = pk.util.logging_integer(platform_config.logging);
}
if (platform_config.url){
config.sts.httpsServerUrl = platform_config.url;
}
if (platform_config.https_certificate_filename){
config.sts.https_certificate_filename = platform_config.https_certificate_filename;
}
if (platform_config.feature_web_path){
config.feature_web_path = platform_config.feature_web_path;
}
if (platform_config.filedb_data_path){
config.filedb_data_path = platform_config.filedb_data_path;
}
if (platform_config.listener_path){
config.sts.listener_path = platform_config.listener_path;
}
}