UNPKG

oidc-lib

Version:

A library for creating OIDC Service Providers

462 lines (396 loc) 15.2 kB
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; } }