Skip to content

Commit

Permalink
Make issuer and verifier input more dynamic
Browse files Browse the repository at this point in the history
Fixes #15

Signed-off-by: Mirko Mollik <[email protected]>
  • Loading branch information
Mirko Mollik committed May 1, 2024
1 parent 5918431 commit 7ee6e5f
Show file tree
Hide file tree
Showing 13 changed files with 617 additions and 26 deletions.
1 change: 1 addition & 0 deletions apps/issuer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-list-routes": "^1.2.1",
"joi": "^17.13.0",
"jose": "^5.2.4",
"passport-azure-ad": "^4.3.5",
"passport-http-bearer": "^1.0.1",
Expand Down
22 changes: 22 additions & 0 deletions apps/issuer/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Joi from 'joi';
import 'dotenv/config';

/**
* Define the environment variables schema
*/
const envVarsSchema = Joi.object()
.keys({
PORT: Joi.number().default(3000),
CONFIG_RELOAD: Joi.boolean().default(false),
ISSUER_BASE_URL: Joi.string().required(),
NODE_ENVIRONMENT: Joi.string()
.valid('development', 'production')
.default('development'),
})
.unknown();

const { error, value: envVars } = envVarsSchema.validate(process.env);

if (error) {
throw new Error(`Config validation error: ${error.message}`);
}
16 changes: 15 additions & 1 deletion apps/issuer/src/issuer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { CredentialSchema } from './types.js';

/**
* The issuer class is responsible for managing the credentials and the metadata of the issuer.
* In case the CONFIG_REALOD environment variable is set, the issuer will reload the configuration every time a method is called.
*/
export class Issuer {
/**
* The metadata of the issuer.
*/
private metadata: CredentialIssuerMetadataOpts;
private metadata!: CredentialIssuerMetadataOpts;

/**
* The credentials supported by the issuer.
Expand All @@ -21,6 +22,10 @@ export class Issuer {
* Creates a new instance of the issuer.
*/
constructor() {
this.loadConfig();
}

private loadConfig() {
//instead of reading at the beginning, we could implement a read on demand.
this.metadata = JSON.parse(
readFileSync(join('templates', 'metadata.json'), 'utf-8')
Expand Down Expand Up @@ -54,6 +59,9 @@ export class Issuer {
* @returns
*/
getCredential(id: string) {
if (process.env.CONFIG_RELOAD) {
this.loadConfig();
}
const credential = this.credentials.get(id);
if (!credential) {
throw new Error(`The credential with the id ${id} is not supported.`);
Expand All @@ -67,6 +75,9 @@ export class Issuer {
* @returns
*/
getDisclosureFrame(id: string) {
if (process.env.CONFIG_RELOAD) {
this.loadConfig();
}
const credential = this.credentials.get(id);
if (!credential) {
throw new Error(`The credential with the id ${id} is not supported.`);
Expand All @@ -78,6 +89,9 @@ export class Issuer {
* Returns the metadata of the issuer.
*/
getMetadata() {
if (process.env.CONFIG_RELOAD) {
this.loadConfig();
}
return this.metadata;
}
}
3 changes: 2 additions & 1 deletion apps/issuer/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import './config.js';
import { ES256, digest, generateSalt } from '@sd-jwt/crypto-nodejs';
import { SDJwtVcInstance } from '@sd-jwt/sd-jwt-vc';
import {
Expand All @@ -18,7 +19,7 @@ import {
import { OID4VCIServer } from '@sphereon/oid4vci-issuer-server';
import { SdJwtDecodedVerifiableCredentialPayload } from '@sphereon/ssi-types';
import { DIDDocument } from 'did-resolver';
import 'dotenv/config';

import expressListRoutes from 'express-list-routes';
import {
JWK,
Expand Down
2 changes: 1 addition & 1 deletion apps/issuer/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
"strict": true /* Enable all strict type-checking options. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": ["src/**/*"]
"include": ["src/**/*", "src/config.ts"]
}
1 change: 1 addition & 0 deletions apps/verifier/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-list-routes": "^1.2.1",
"joi": "^17.13.0",
"jose": "^5.2.4",
"passport-azure-ad": "^4.3.5",
"passport-http-bearer": "^1.0.1",
Expand Down
31 changes: 29 additions & 2 deletions apps/verifier/src/RPManager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ES256, digest } from '@sd-jwt/crypto-nodejs';
import {
EcdsaSignature,
InMemoryRPSessionManager,
JWK,
JWTPayload,
PassBy,
Expand Down Expand Up @@ -29,6 +28,7 @@ import { importJWK, jwtVerify } from 'jose';
import { getKeys, getPublicKey } from './keys.js';
import { EventEmitter } from 'node:events';
import { RPInstance } from './types.js';
import { InMemoryRPSessionManager } from './session-manager.js';

// load the keys
const { privateKey, publicKey } = await getKeys();
Expand All @@ -40,7 +40,9 @@ export const kid = did;
// create the event emitter to listen to events.
export const eventEmitter = new EventEmitter();
//TODO: implement a persistant session manager so reloads don't lose state
export const sessionManager = new InMemoryRPSessionManager(eventEmitter);
export const sessionManager = new InMemoryRPSessionManager(eventEmitter, {
// maxAgeInSeconds: 10,
});

/**
* The RPManager is responsible for managing the relying parties.
Expand All @@ -58,11 +60,36 @@ export class RPManager {
let rp = this.rp.get(id);
if (!rp) {
rp = this.buildRP(id);
// checks every minute if the rp has active sessions. If there is none, the rp is removed. We want to do this so we can update the rp with new input without losing state. This approach could be improved since we are waiting around 4 minutes for the last finished request until the entries are removed.
setInterval(async () => {
this.remove(id);
}, 1000 * 60);
this.rp.set(id, rp);
}
return rp;
}

/**
* Removes a relying party. This is useful when the instance should be restarted with a new definition.
* @param id
*/
async remove(id: string, force = false) {
const rp = this.rp.get(id);
if (!rp) {
return;
}
if (
!force &&
//the limit for a session is 5 minutes, so after this a session becomes idle an can be removed.
!(await (rp.rp.sessionManager as InMemoryRPSessionManager).isIdle())
) {
// we have active sessions, we don't want to remove the rp. But at this point we do not know if they have already finished it. We just know they are not over the maximum defined limit (default 5 minutes).
return;
}
this.rp.delete(id);
console.log('Removed the rp');
}

private buildRP(id: string) {
// create the relying party
const verifierFile = readFileSync(join('templates', `${id}.json`), 'utf-8');
Expand Down
22 changes: 22 additions & 0 deletions apps/verifier/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Joi from 'joi';
import 'dotenv/config';

/**
* Define the environment variables schema
*/
const envVarsSchema = Joi.object()
.keys({
PORT: Joi.number().default(3000),
CONFIG_RELOAD: Joi.boolean().default(false),
VERIFIER_BASE_URL: Joi.string().required(),
NODE_ENVIRONMENT: Joi.string()
.valid('development', 'production')
.default('development'),
})
.unknown();

const { error, value: envVars } = envVarsSchema.validate(process.env);

if (error) {
throw new Error(`Config validation error: ${error.message}`);
}
2 changes: 1 addition & 1 deletion apps/verifier/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import './config.js';
import {
PresentationDefinitionLocation,
SupportedVersion,
} from '@sphereon/did-auth-siop';
import 'dotenv/config';
import expressListRoutes from 'express-list-routes';
import { v4 } from 'uuid';
import { expressSupport } from './server.js';
Expand Down
Loading

0 comments on commit 7ee6e5f

Please sign in to comment.