Skip to content

Commit

Permalink
Add Credentials Config/
Browse files Browse the repository at this point in the history
  • Loading branch information
jason-fox committed Feb 19, 2025
1 parent 6213882 commit 7cad6ed
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 53 deletions.
88 changes: 75 additions & 13 deletions forwarder/bin/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const os = require('os');
const cluster = require('cluster');
const bearerToken = require('express-bearer-token');
const Verifier = require('../lib/verifier');
const ConfigService = require('../lib/configService');
const { createProxyMiddleware } = require('http-proxy-middleware');
const PORT = process.env.PORT || 80;
const clusterWorkerSize = os.cpus().length;
Expand All @@ -25,15 +26,16 @@ const proxy = createProxyMiddleware({
}
});

function initForwarder(text) {
function initForwarder(config, text) {
const app = express();
app.get('/status', (req, res) => {
res.status(200).send();
});

if (verify) {
app.use(bearerToken());
app.use('/', Verifier.Verifier);
verifier = new Verifier.Verifier(config);
app.use('/', verifier.verify);
}

app.use('/', proxy);
Expand All @@ -42,19 +44,79 @@ function initForwarder(text) {
});
}

if (clusterWorkerSize > 1) {
if (cluster.isMaster) {
for (let i = 0; i < clusterWorkerSize; i++) {
cluster.fork();
/**
* Check that the IDM is responding and the PEP is recognized within the IDM
* @return an auth token representing the PEP itself to be used in subsequent requests
*/
function connect() {
let retry = 20;
return new Promise((resolve, reject) => {
const connect_with_retry = async () => {
try {
await ConfigService.checkConnectivity();
debug(
`Credentials Config Service is now available - requesting config for ${tenant}`
);

ConfigService.getConfig(tenant)
.then(response => {
return resolve(response);
})
.catch(error => {
return reject(
'Credentials Config Service rejected config: ' + error.message
);
});
} catch (e) {
debug(e.message);
retry--;
if (retry === 0) {
return reject(
'Credentials Config Service is not available. Giving up after 20 attempts'
);
}
debug('retry after 5 seconds.');
//eslint-disable-next-line snakecase/snakecase
setTimeout(connect_with_retry, 5000);
}
};
connect_with_retry();
});
}

function startServer(config) {
if (clusterWorkerSize > 1) {
if (cluster.isMaster) {
for (let i = 0; i < clusterWorkerSize; i++) {
cluster.fork();
}
cluster.on('exit', function(worker) {
debug('Worker', worker.id, ' has exited.');
});
} else {
initForwarder(
config,
`Server listening on port ${PORT} and worker ${process.pid}`
);
}
cluster.on('exit', function(worker) {
debug('Worker', worker.id, ' has exited.');
});
} else {
initForwarder(`Server listening on port ${PORT} and worker ${process.pid}`);
initForwarder(
config,
`Server listening on port ${PORT} with the single worker ${process.pid}`
);
}
} else {
initForwarder(
`Server listening on port ${PORT} with the single worker ${process.pid}`
}

if (verify) {
connect().then(
config => {
startServer(config);
},
err => {
debug(err);
process.exit(1);
}
);
} else {
startServer(null);
}
29 changes: 29 additions & 0 deletions forwarder/lib/configService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const StatusCodes = require('http-status-codes').StatusCodes;
const configServiceURL = process.env.CONFIG_SERVICE || 'localhost:8081';

/**
* Check that Keyrock is responding to requests
*/
exports.checkConnectivity = function() {
return fetch(`http://${configServiceURL}/service`);
};

exports.getConfig = function(tenant) {
return new Promise(function(resolve, reject) {
console.log(`http://${configServiceURL}/service/${tenant}`);
const fetchPromise = fetch(`http://${configServiceURL}/service/${tenant}`);
fetchPromise
.then(response => {
if (response.status === StatusCodes.NOT_FOUND) {
resolve({});
}
return response.json();
})
.then(config => {
resolve(config.oidcScopes[config.defaultOidcScope]);
})
.catch(error => {
return reject(error);
});
});
};
147 changes: 107 additions & 40 deletions forwarder/lib/verifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,54 +14,121 @@ const WebDIDResolver = require('web-did-resolver');
const VerifiableCredentials = require('did-jwt-vc');

const resolver = new DIDResolver.Resolver(WebDIDResolver.getResolver());
const Verifier = function(req, res, next) {
if (!req.token) {
deny(res, 'message', 'type', `Bearer realm="${tenant}"`);

class Verifier {
constructor(config) {
this.config = config;
this.verify = this.verify.bind(this);
}
VerifiableCredentials.verifyPresentation(req.token, resolver)
.then(presentation => {
debug('Presentation');
debug(`issued by: ${presentation.issuer}`);
for (const credential of presentation.verifiablePresentation
.verifiableCredential) {
VerifiableCredentials.verifyCredential(
credential.proof.jwt,
resolver
).then(verifiedVC => {
const vc = {
type: credential.type,
data: credential.credentialSubject,
sub: verifiedVC.payload.sub,
iss: verifiedVC.payload.iss,
nbf: verifiedVC.payload.nbf
};

debug(`${vc.type}`);
debug(` nbf: ${vc.nbf}`);
debug(` iss: ${vc.iss}`);
debug(` sub: ${vc.sub}`);
debug(` data: ${JSON.stringify(vc.data)}\r\n`);
verify(req, res, next) {
if (!req.token) {
deny(res, 'message', 'type', `Bearer realm="${tenant}"`);
}

VerifiableCredentials.verifyPresentation(req.token, resolver)
.then(presentation => {
debug('Presentation');
debug(`issued by: ${presentation.issuer}`);
const credentialsPromises = [];

for (const credential of presentation.verifiablePresentation
.verifiableCredential) {
credentialsPromises.push(
verifyCredential(
credential.proof.jwt,
credential.type,
credential.credentialSubject
)
);
}

Promise.all(credentialsPromises).then(credentials => {
const trustedIssuersPromises = [];
for (const credential of credentials) {
const trustedList = getTrustedIssuerHost(
credential.type,
this.config
);
trustedIssuersPromises.push(
verifyTrustedIssuer(credential, trustedList)
);
}

Promise.all(trustedIssuersPromises).then(values => {
console.log(values);
next();
});
});
}

next();
})
.catch(error => {
deny(res, error.message, 'type', `Bearer realm="${tenant}"`);
});
};
// debug(`${vc.type}`);
// debug(` nbf: ${vc.nbf}`);
// debug(` iss: ${vc.iss}`);
// debug(` sub: ${vc.sub}`);
// debug(` data: ${JSON.stringify(vc.data)}\r\n`);
})
.catch(error => {
deny(res, error.message, 'type', `Bearer realm="${tenant}"`);
});
}
}

/*
function verifyCredential(jwt, type, data) {
return new Promise(function(resolve, reject) {
let vc = null;
VerifiableCredentials.verifyCredential(jwt, resolver)
.then(verifiedVC => {
const vc = {
type: type,
data: data,
sub: verifiedVC.payload.sub,
iss: verifiedVC.payload.iss,
nbf: verifiedVC.payload.nbf
};
return resolve(vc);
})
.catch(error => {
return reject(error);
});
});
}

async function verifyPresentation(jwt) {
const verifiedVP = await VerifiableCredentials.verifyPresentation(jwt, resolver);
return verifiedVP;
function getTrustedIssuerHost(type, config) {
let host = null;
for (const item of config) {
if (type.includes(item.type)) {
host = item.trustedIssuersLists[0];
break;
}
}
return host;
}

async function verifyCredential(jwt) {
const verifiedVC = await VerifiableCredentials.verifyCredential(jwt, resolver);
return verifiedVC;
}*/
// trusted-issuers-list
function verifyTrustedIssuer(vc, trustedList) {
return new Promise(function(resolve, reject) {
//console.log(`${trustedList}/v4/issuers/${vc.iss}`);
const fetchPromise = fetch(`${trustedList}/v4/issuers/${vc.iss}`);
fetchPromise
.then(response => {
if (response.status === StatusCodes.NOT_FOUND) {
vc.trusted = false;
vc.claims = [];
resolve(vc);
}
return response.json();
})
.then(payload => {
const claims = JSON.parse(atob(payload.attributes[0].body));
vc.trusted = vc.type.includes(claims.credentialsType);
vc.claims = vc.trusted ? claims.claims : [];
resolve(vc);
})
.catch(error => {
return reject(error);
});
});
}

/**
* Return an "Access Denied" response
Expand Down

0 comments on commit 7cad6ed

Please sign in to comment.