UNPKG

oidc-lib

Version:

A library for creating OIDC Service Providers

523 lines (445 loc) 15.8 kB
var fs = require('fs'); var path = require('path'); var base64url = require('base64url'); var node_input = require('./nodeInput'); var fetch = require('node-fetch'); var util = require('./util') const lib_data = 'oidc_lib_data'; var debug = false; var proceed = false; const moniker_signature = /(?:<--)(\w*)(?:-->)/g; var monikers = { "ISSUER_HOST": "https://virtual.itsourweb.org:3000", "CLIENT_API": "https://virtual.itsourweb.org:3001", "APP_LAUNCHER": "https://virtual.itsourweb.org:3002", "SMART_CREDENTIAL_HOST": "https://virtual.itsourweb.org:3003", "GATEWAY": "https://virtual.itsourweb.org:3004", "UHN": "https://virtual.itsourweb.org:3005", "DENTIST": "https://virtual.itsourweb.org:3006", "WALLET": "https://virtual.itsourweb.org:3007" } var listeners = {}; startup(); async function startup(){ try{ if (process.argv.length < 3){ console.log("usage:"); console.log('node platform_import.js platform_directory feature moniker.json [proceed]'); console.log('help: platform_import_help.html'); process.exit(0); } if (process.argv.length < 5){ console.log("Error: missing arguments - usage is:"); console.log('node platform_import.js platform_directory feature moniker.json [proceed]'); process.exit(0); } var platform_directory = process.argv[2]; var regex = /(?:(?:([A-Z]:)?))(?:(\\|\/){1})(?:.)*/g; if (!platform_directory.match(regex)){ platform_directory = path.join(process.cwd(), platform_directory); } console.log('Platform directory (where feature will be installed) is ' + platform_directory); var feature = process.argv[3]; console.log('Feature to be installed is ' + feature); var moniker_file = process.argv[4]; if (!moniker_file.match(regex)){ moniker_file = path.join(process.cwd(), moniker_file); } console.log('Feature will be installed using moniker file ' + moniker_file); try{ var monikerString = fs.readFileSync(moniker_file, 'utf8'); monikers = JSON.parse(monikerString); console.log('Moniker file processed: ' + moniker_file); } catch(err){ console.log('Fatal error opening moniker file: ' + moniker_file); process.exit(1); } var replacementInfo = { signature: moniker_signature, monikers: monikers } var option = process.argv[5]; proceed = false; if (option && option === 'proceed'){ proceed = true; console.log('You were not prompted for approval because "proceed" was specified') } else{ console.log('You are being prompted for approval because "proceed" has NOT been specified'); } var possibleError; await applyFeature(platform_directory, feature, replacementInfo); } catch(err){ throw(err); } } async function applyFeature(platform_directory, feature, replacementInfo){ switch (feature){ case ('app_launcher'): case ('client_api'): case ('gateway'): await processAddition(platform_directory, replacementInfo, 'USE_FEATURE_NAME', feature); break; case ('ohip'): case ('ohip_covid'): case ('ohip_smarthealthcard'): await processAddition(platform_directory, replacementInfo, 'ISSUER_HOST', feature); break; case ('smart-credential'): await processAddition(platform_directory, replacementInfo, 'SMART_CREDENTIAL_HOST', feature); break; case ('wallet'): await processAddition(platform_directory, replacementInfo, 'WALLET', feature); break; case ('uhn'): await processAddition(platform_directory, replacementInfo, 'UHN', feature); break; case ('dentist'): await processAddition(platform_directory, replacementInfo, 'DENTIST', feature); break; case 'httpsServerUrl': case 'new_issuer_name': case 'smart_credential_name': break; default: console.log('Undefined feature in apply_features: ' + feature); // process.exit(1); } } async function processAddition(platform_directory, replacementInfo, moniker, feature){ try{ var includeJsCss = false; var possibleError; if (moniker === 'USE_FEATURE_NAME'){ moniker = feature.toUpperCase(); } add_listener(moniker); updateListenerFile(platform_directory, listeners); var feature_file = feature.endsWith('.json') ? feature : feature + '.json'; var feature_path = path.join(platform_directory, feature_file); possibleError = 'Feature file not found: ' + feature_path; var input_string = fs.readFileSync(feature_path, 'utf8'); possibleError = 'Error parsing feature: ' + input_string; var input = JSON.parse(input_string); if (input.oidc_lib_version){ var import_lib_version = input.oidc_lib_version.replace('^', ''); if (!proceed){ var ni = new node_input(); proceed = await ni.question( 'Feature was created with oidc-lib version ' + import_lib_version + '. Press "Y" to proceed, "N" to exit.', 'trueFalse'); if (!proceed){ process.exit(1); } } } var issuer; var config = decodeB64Element('oidc_config.json', input.code['oidc_config.json'], replacementInfo); var config_files = config.config_files; for (var inputKey in input){ switch (inputKey){ case 'issuer': issuer = input.issuer; break; case 'code': var directoryContent = input[inputKey]; possibleError = 'Error applying platform_directory: [' + inputKey + ']'; // applyDirectory(config_files, codePath, input.code); applyDirectory(platform_directory, config_files, replacementInfo, feature, issuer, directoryContent); break; case 'description': case 'oidc_lib_version': break; default: var skip = false; var overwrite = true; switch (inputKey){ case 'web/' + issuer: issuer = 'web/' + issuer; break; case 'web/js': case 'web/css': overwrite = true; if (!includeJsCss){ skip = true; } break; case 'web/code': inputKey = 'web/' + issuer; break; case 'views': overwrite = true; break; } var directoryContent = input[inputKey]; possibleError = 'Error applying platform_directory: [' + inputKey + ']'; // applyDirectory(platform_directory, config_files, replacementInfo, location, input[inputKey]); applyDirectory(platform_directory, config_files, replacementInfo, feature, issuer, directoryContent); break; } } console.log("Done"); process.exit(0); } catch(err){ console.log(possibleError); console.log(err.code); process.exit(-1); } } function applyDirectory(platform_directory, config_files, replacementInfo, feature_short_name, issuer, directoryContent){ var platform_directory = platform_directory; var platform_directoryLength = platform_directory.length; replacementInfo.monikers['ISSUER'] = issuer; var directoryName = path.join(platform_directory, issuer); recurseDirectory(config_files, feature_short_name, replacementInfo, directoryName, issuer, directoryContent); function recurseDirectory(config_files, feature_short_name, replacementInfo, directoryName, issuer, directoryContent){ try{ for (var key in directoryContent){ var content = directoryContent[key]; if (typeof content === 'string'){ var encoding = encodingFromExtension(key); var contentStringOrBuffer; if (encoding){ contentStringOrBuffer = base64url.decode(content); } else{ var contentStringOrBuffer = base64url.toBuffer(content); } mkDirectoriesInPath(directoryName); var filename; var safe_filename; if (key === (feature_short_name + '.js') && issuer !== feature_short_name){ filename = path.join(directoryName, feature_short_name + '.js'); safe_filename = path.join(directoryName, issuer + '.js'); } else{ filename = path.join(directoryName, key); safe_filename = filename; } var relativePath = filename.substr(platform_directoryLength).replace(/\\/g, '/'); if (fs.existsSync(filename) && config_files && config_files.includes(relativePath)){ console.log('** not overwriting config_file ' + filename + ' - will write with .new extension'); safe_filename = filename + '.new'; } if (replacementInfo && encoding){ contentStringOrBuffer = processReplace(contentStringOrBuffer, replacementInfo); } fs.writeFileSync(safe_filename, contentStringOrBuffer, encoding); } else if (typeof content === 'object'){ var subDirectoryName = path.join(directoryName, key); mkDirectoriesInPath(subDirectoryName); recurseDirectory(config_files, feature_short_name, replacementInfo, subDirectoryName, key, content); } } } catch(err){ console.log('Error in recurseDirectory for ' + directoryName); console.log('Content will be missing'); } } } /* function applyDirectory(config_files, directoryName, directoryContent){ var _workingDirectory = process.cwd(); var _workingDirectoryLength = _workingDirectory.length; recurseDirectory(config_files, directoryName, directoryContent); function recurseDirectory(config_files, directoryName, directoryContent){ for (var key in directoryContent){ var content = directoryContent[key]; if (typeof content === 'string'){ var buffer = base64url.toBuffer(content); util.mkDirectoriesInPath(directoryName); var filename = path.join(directoryName, key); var relativePath = filename.substr(_workingDirectoryLength).replace(/\\/g, '/'); var safe_filename = filename; if (fs.existsSync(filename) && config_files && config_files.includes(relativePath)){ console.log('** not overwriting config_file ' + filename + ' - will write with .new extension'); safe_filename = filename + '.new'; } fs.writeFileSync(safe_filename, buffer); } else if (typeof content === 'object'){ var subDirectoryName = path.join(directoryName, key); util.mkDirectoriesInPath(subDirectoryName); recurseDirectory(config_files, subDirectoryName, content); } } } } */ function decodeB64Element(filename, b64Element, replacementInfo){ try{ var encoding = encodingFromExtension(filename); var possibleError = ' (decoding) '; var decoded = base64url.decode(b64Element); if (replacementInfo && encoding){ decoded = processReplace(decoded, replacementInfo); } possibleError = ' (parsing) '; var config = JSON.parse(decoded); return config; } catch(err){ console.log('ERROR decoding B64Element ' + possibleError + ' - ' + filename); } } function copyDirectory(sourceDirName, destDirName, replaceInfo){ mkDirectoriesInPath(destDirName); var dirents = fs.readdirSync(sourceDirName, {withFileTypes: true}); for (var i=0; i<dirents.length; i++){ var dirent = dirents[i]; if (dirent.isFile()){ var name = dirent.name; var encoding = encodingFromExtension(name); var srcPath = path.join(sourceDirName, name); var destPath = path.join(destDirName, name); var content = fs.readFileSync(srcPath, encoding); if (replaceInfo && encoding){ content = processReplace(content, replaceInfo); } fs.writeFileSync(destPath, content, encoding); } } for (var i=0; i<dirents.length; i++){ var dirent = dirents[i]; if (dirent.isDirectory()){ copyDirectory(path.join(sourceDirName, dirent.name), path.join(destDirName, dirent.name), replaceInfo); } } } function processReplace(content, replaceInfo){ var groups; var new_content = ''; var last_index = 0; while (groups = replaceInfo.signature.exec(content)){ var value = groups[1]; new_content += content.substr(last_index, groups.index - last_index) + replaceInfo.monikers[value]; last_index = replaceInfo.signature.lastIndex; } if (last_index){ new_content += content.substr(last_index); content = new_content; } return content; } function encodingFromExtension(name){ var encoding; var extension = name.substr(name.lastIndexOf('.') + 1); var binaries = [ 'jpg', 'png', 'jpeg', 'gif', 'bmp', 'eot', 'ttf', 'svg', 'woff', 'woff2' ]; if (binaries.includes(extension)){ encoding = undefined; } else{ encoding = 'utf8'; } return encoding; } function add_listener(moniker){ moniker = moniker.toUpperCase(); var url = monikers[moniker]; if (!url){ console.log('FATAL ERROR: attempt to add undefined moniker: ' + moniker); process.exit(1); } if (!listeners[moniker]){ listeners[moniker] = url; console.log('Added listener [' + moniker + '] to app config as: ' + url); } } function updateListenerFile(app_directory, listeners){ try{ var current_listeners = {}; var listener_dir = path.join(app_directory, 'oidc_lib_data'); mkDirectoriesInPath(listener_dir); var listener_file = path.join(listener_dir, 'listeners.json'); var possibleError = 'listen file does not exist...'; if (fs.existsSync(listener_file)){ possibleError = 'cannot read listener file'; var listenersString = fs.readFileSync(listener_file); possibleError = 'cannot parse listener file'; current_listeners = JSON.parse(listenersString); } for (var key in listeners){ current_listeners[key] = listeners[key]; } fs.writeFileSync(listener_file, JSON.stringify(current_listeners, null, 2), 'utf8'); console.log('Listeners file written: ' + listener_file); } catch(err){ pk.util.log_error('error in updateListenerFile: ' + possibleError, err); } } function readHttpsListeners(){ try{ possibleError = ''; if (!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 = 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); } } // node can run under linux or windows function usingNode(){ return (typeof window === "undefined"); } // unix file system only possible running node function isUnixFilesystem(){ // node rather than browser && /usr/bin in the path return usingNode() && process.env.PATH.indexOf("/usr/bin") >= 0; } // WARNING *** NOT THE SAME AS sts utils because of fs function mkDirectoriesInPath(target) { var sep; var firstSeg = true; if (isUnixFilesystem()){ target = forwardSlash(target); sep = '/'; } else{ target = backSlash(target); sep = '\\'; } var segments = target.split(sep); var path = ''; var prefix = ''; for (var i=0; i < segments.length; i++){ path += prefix + segments[i]; prefix = sep; if (firstSeg){ firstSeg = false; continue; } prefix = sep; if (!fs.existsSync(path)){ fs.mkdirSync(path); } } } // forwardSlash is pk.util.slash_normalize - adjusts file paths written // for windows so they work on unix as well. function forwardSlash(windows_path){ if (isUnixFilesystem()){ windows_path = windows_path.replace(/\\/g, '/');; } return windows_path; } // backSlash is slash_denormalize - adjusts file paths written // for unix so they work on windows as well. function backSlash(unix_path){ if (!isUnixFilesystem()){ unix_path = unix_path.replace(/\//g, '\\');; } return unix_path; }