oidc-lib
Version:
A library for creating OIDC Service Providers
523 lines (445 loc) • 15.8 kB
JavaScript
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;
}