Skip to content

Commit

Permalink
feat: testing irma api client
Browse files Browse the repository at this point in the history
  • Loading branch information
marnixdessing committed Oct 25, 2022
1 parent 0a622f3 commit 4a63d81
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 44 deletions.
12 changes: 12 additions & 0 deletions .projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions .projenrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const project = new awscdk.AwsCdkTypeScriptApp({
'openid-client',
'@types/cookie',
'cookie',
'@types/aws-lambda',

], /* Runtime dependencies of this module. */
// description: undefined, /* The description is just a string that helps people understand the purpose of the package. */
Expand All @@ -36,6 +37,8 @@ const project = new awscdk.AwsCdkTypeScriptApp({
'@playwright/test',
'aws-sdk-client-mock',
'jest-raw-loader',
'axios-mock-adapter',
'jest-aws-client-mock',
], /* Build dependencies for this module. */
depsUpgradeOptions: {
workflowOptions: {
Expand Down
3 changes: 3 additions & 0 deletions package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions src/app/code/AwsUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
SecretsManagerClient,
GetSecretValueCommand,
} from '@aws-sdk/client-secrets-manager';

export class AwsUtil {

/**
* Retrieves a sercet from the secrets store given an ARN
* @param arn
* @returns
*/
async getSecret(arn: string) {
if (!arn) {
throw new Error('No ARN provided');
}
const secretsManagerClient = new SecretsManagerClient({});
const command = new GetSecretValueCommand({ SecretId: arn });
const data = await secretsManagerClient.send(command);
if (data?.SecretString) {
return data.SecretString;
}
throw new Error('No secret value found');
}

}
68 changes: 25 additions & 43 deletions src/app/code/IrmaApi.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import {
SecretsManagerClient,
GetSecretValueCommand,
} from '@aws-sdk/client-secrets-manager';
import { aws4Interceptor } from 'aws4-axios';
import axios, { Axios, AxiosRequestConfig } from 'axios';
import axios, { Axios } from 'axios';
import { AwsUtil } from './AwsUtil';

export class IrmaApi {

Expand Down Expand Up @@ -33,10 +30,11 @@ export class IrmaApi {
if (!process.env.IRMA_API_ACCESS_KEY_ID_ARN || !process.env.IRMA_API_SECRET_KEY_ARN || !process.env.IRMA_API_KEY_ARN) {
throw Error('Clould not initialize IRMA API client');
}
this.apiKey = await this.getSecret(process.env.IRMA_API_KEY_ARN);
const util = new AwsUtil();
this.apiKey = await util.getSecret(process.env.IRMA_API_KEY_ARN);
this.credentials = {
accessKeyId: await this.getSecret(process.env.IRMA_API_ACCESS_KEY_ID_ARN),
secretAccessKey: await this.getSecret(process.env.IRMA_API_SECRET_KEY_ARN),
accessKeyId: await util.getSecret(process.env.IRMA_API_ACCESS_KEY_ID_ARN),
secretAccessKey: await util.getSecret(process.env.IRMA_API_SECRET_KEY_ARN),
};
}

Expand All @@ -50,55 +48,38 @@ export class IrmaApi {
};
}

async getSecret(arn: string) {
if (!arn) {
throw new Error('No ARN provided');
}
const secretsManagerClient = new SecretsManagerClient({});
const command = new GetSecretValueCommand({ SecretId: arn });
const data = await secretsManagerClient.send(command);
if (data?.SecretString) {
return data.SecretString;
}
throw new Error('No secret value found');
}

async startSession(brpData: any) {

const irmaIssueRequest: AxiosRequestConfig = {
method: 'POST',
url: `https://${this.host}/session`,
data: this.constructIrmaIssueRequest(brpData),
headers: {
'irma-authorization': this.apiKey,
'Content-type': 'application/json',
},
};

return this.makeSignedRequest(irmaIssueRequest, 'De IRMA sessie kon niet worden gestart.');

const irmaIssueRequest = this.constructIrmaIssueRequest(brpData);
return this.doSignedPostRequest('session', irmaIssueRequest, 'De IRMA sessie kon niet worden gestart.');
}

private getSigningClient(): Axios {
if(!this.credentials.accessKeyId || !this.credentials.secretAccessKey){
throw new Error("API client is not configured propperly, missing AWS signature credentials");
if (!this.credentials.accessKeyId || !this.credentials.secretAccessKey) {
throw new Error('API client is not configured propperly, missing AWS signature credentials');
}
const interceptor = aws4Interceptor({
region: 'eu-west-1',
service: 'execute-api',
}, this.credentials);
const client = new Axios();
const client = axios.create({
baseURL: `https://${this.host}`,
timeout: 2000,
headers: {
'irma-authorization': this.apiKey,
'Content-type': 'application/json',
},
});
client.interceptors.request.use(interceptor);
return client;
}

private async makeSignedRequest(request: AxiosRequestConfig, errorMsg: string) {
console.debug('Starting signed request:', JSON.stringify(request, null, 4));
private async doSignedPostRequest(path: string, data: any, errorMsg: string) {
console.debug('Starting signed POST request:', path);

try {
const client = this.getSigningClient();
let resp = await client.request(request);

const resp = await client.post(path, data);
if (resp.data) {
console.debug('Response data:', resp.data);
return resp.data;
Expand All @@ -109,7 +90,8 @@ export class IrmaApi {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(`http status for ${request.url}: ${error.response?.status}`);
console.log(`http status for ${path}: ${error.response?.status}`);
console.debug(error.response.data);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
Expand All @@ -120,13 +102,13 @@ export class IrmaApi {
console.error(error.message);
}
} else {
console.error(error.message);
console.error('Non axios error occured:', error);
}
return { error: errorMsg };
}
}

constructIrmaIssueRequest(brpData: any) {
private constructIrmaIssueRequest(brpData: any) {

// Get persoonsgegevens
const gegevens = brpData.Persoon.Persoonsgegevens;
Expand Down
2 changes: 1 addition & 1 deletion test/__snapshots__/main.test.ts.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

139 changes: 139 additions & 0 deletions test/app/irma.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { IrmaApi } from '../../src/app/code/IrmaApi';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { AwsUtil } from '../../src/app/code/AwsUtil';
import * as dotenv from 'dotenv'
dotenv.config()

const axiosMock = new MockAdapter(axios);

const getSecretMock = jest.spyOn(AwsUtil.prototype, 'getSecret')
.mockImplementation(async (arn: string) => {
return `secret-${arn}`;
});

beforeAll(() => {
console.log = jest.fn();
console.info = jest.fn();
console.debug = jest.fn();
console.error = jest.fn();
});

beforeEach(() => {
axiosMock.reset();
});

test('Check if irma api adds aws4-singature and irma-authorization header', async () => {
axiosMock.onPost('/session').reply(200, sessionResponse);

const client = new IrmaApi();
client.manualInit('gw-test.nijmegen.nl', true, 'someid', 'somesecretkey', 'irma-autharizaiton-header');
const imraResp = await client.startSession(brpData);

// Check if the response is correct
expect(imraResp).toStrictEqual(sessionResponse);
// Check request config in history
const request = axiosMock.history['post'][0];
expect(request).not.toBe(undefined);
// Validate if the headers are set
expect(request.headers).not.toBe(undefined);
if (request.headers) {
expect(request.headers['irma-authorization']).toBe('irma-autharizaiton-header');
expect(request.headers['Authorization']).not.toBeUndefined();
expect(request.headers['X-Amz-Date']).not.toBeUndefined();
}
});

test('Initialization', async () => {
process.env.IRMA_API_ACCESS_KEY_ID_ARN = 'key-id-arn';
process.env.IRMA_API_SECRET_KEY_ARN = 'secret-arn';
process.env.IRMA_API_KEY_ARN = 'key-arn';
process.env.IRMA_API_HOST = 'gw-test.nijmegen.nl';
process.env.IRMA_API_DEMO = 'demo';


const api = new IrmaApi();
await api.init();
expect(api.getHost()).toBe(process.env.IRMA_API_HOST);
expect(getSecretMock).toHaveBeenCalledTimes(3);
});

test('Initialization and test', async () => {
process.env.IRMA_API_ACCESS_KEY_ID_ARN = 'key-id-arn';
process.env.IRMA_API_SECRET_KEY_ARN = 'secret-arn';
process.env.IRMA_API_KEY_ARN = 'key-arn';
process.env.IRMA_API_HOST = 'gw-test.nijmegen.nl';
process.env.IRMA_API_DEMO = 'demo';

axiosMock.onPost('/session').reply(200, sessionResponse);

const api = new IrmaApi();
await api.init();
expect(getSecretMock).toHaveBeenCalledTimes(3);

const irmaResp = await api.startSession(brpData);

// Check if the response is correct
expect(irmaResp).toStrictEqual(sessionResponse);
// Check request config in history
const request = axiosMock.history['post'][0];
expect(request).not.toBe(undefined);
// Validate if the headers are set
expect(request.headers).not.toBe(undefined);
if (request.headers) {
expect(request.headers['irma-authorization']).toBe('secret-key-arn');
expect(request.headers['Authorization']).not.toBeUndefined();
expect(request.headers['X-Amz-Date']).not.toBeUndefined();
}

});


const brpData = {
"Persoon": {
"BSN": {
"BSN": "900026236"
},
"Persoonsgegevens": {
"Voorletters": "H.",
"Voornamen": "Hans",
"Voorvoegsel": "de",
"Geslachtsnaam": "Jong",
"Achternaam": "de Jong",
"Naam": "H. de Jong",
"Geboortedatum": "01-01-1956",
"Geslacht": "M",
"NederlandseNationaliteit": "Ja",
"Geboorteplaats": "Nijmegen",
"Geboorteland": "Nederland"
},
"Adres": {
"Straat": "Kelfkensbos",
"Huisnummer": "80",
"Gemeente": "Nijmegen",
"Postcode": "6511 RN",
"Woonplaats": "Nijmegen"
},
"ageLimits": {
"over12": "Yes",
"over16": "Yes",
"over18": "Yes",
"over21": "Yes",
"over65": "Yes"
}
}
}

const sessionResponse = {
sessionPtr: {
u: 'https://gw-test.nijmegen.nl/irma/session/anothertoken',
irmaqr: 'issuing'
},
token: 'sometoken',
frontendRequest: {
authorization: 'blabla',
pairingHint: true,
minProtocolVersion: '1.0',
maxProtocolVersion: '1.1'
}
}
1 change: 1 addition & 0 deletions test/app/login.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ beforeAll(() => {
global.console.error = jest.fn();
global.console.time = jest.fn();
global.console.log = jest.fn();
global.console.debug = jest.fn();
}

// Set env variables
Expand Down
Loading

0 comments on commit 4a63d81

Please sign in to comment.