Skip to content
This repository has been archived by the owner on Mar 27, 2021. It is now read-only.

Commit

Permalink
feat: provide basic implementation for data requests and submissions
Browse files Browse the repository at this point in the history
  • Loading branch information
BerniWittmann committed Mar 21, 2021
1 parent be63d3a commit b330d53
Show file tree
Hide file tree
Showing 16 changed files with 233 additions and 12 deletions.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@semantic-release/npm": "^7.0.0",
"@semantic-release/release-notes-generator": "^9.0.0",
"@types/jest": "^25.1.0",
"@types/node": "^14.14.35",
"@typescript-eslint/eslint-plugin": "^2.15.0",
"@typescript-eslint/parser": "^2.15.0",
"eslint": "^6.8.0",
Expand All @@ -41,5 +42,8 @@
"type": "git",
"url": "https://github.com/InOG-projects/IRIS-library-js.git"
},
"version": "1.0.0"
"version": "1.0.0",
"dependencies": {
"axios": "^0.21.1"
}
}
11 changes: 3 additions & 8 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { greet } from '.';
import Iris from '.';

describe('index', () => {
describe('greet', () => {
it('should return greeting with name specified', () => {
const testName = 'testName';
const greetingPhrase = greet(testName);

expect(greetingPhrase).toEqual(`Hello ${testName}!`);
});
it('provides the Iris class', () => {
expect(new Iris({})).toBeDefined();
});
});
5 changes: 2 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export const greet = (name: string): string => `Hello ${name}!`;

console.log(greet('World'));
import Iris from './lib/';
export default Iris;
67 changes: 67 additions & 0 deletions src/lib/Iris.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import axios, { AxiosInstance } from 'axios';

import { encryptData } from './crypto';
import { getNameCheckHash, getBirthDateCheckHash } from './util';

import IrisOptions from '../types/IrisOptions';
import IrisCode from '../types/IrisCode';
import IrisDataRequest from '../types/IrisDataRequest';
import IrisCodeKeyMap from '../types/IrisCodeKeyMap';
import IrisDataRequestDTO from '../types/dto/IrisDataRequestDTO';
import IrisContactsEvents from '../types/IrisContactsEvents';
import IrisContactsEventsSubmissionDTO from '../types/dto/IrisContactsEventsSubmissionDTO';
import IrisUserInfo from '../types/IrisUserInfo';

const defaultOptions: IrisOptions = {
baseUrl: '',
};

export default class Iris {
axiosInstance: AxiosInstance;
codeKeyMap: IrisCodeKeyMap;

constructor(options: Partial<IrisOptions>) {
this.codeKeyMap = new Map();
const opts: IrisOptions = Object.assign(defaultOptions, options);
this.axiosInstance = axios.create({
baseURL: opts.baseUrl,
});
}

async getDataRequest(code: IrisCode): Promise<IrisDataRequest> {
const response = await this.axiosInstance.get(`/data-requests/${code}`);
if (response.status !== 200) {
console.error('IRIS Gateway responded the following data', response.data);
throw new Error(`Request failed with status Code ${response.status}`);
}
const dataRequest = response.data as IrisDataRequestDTO;
this.codeKeyMap.set(code, {
keyOfHealthDepartment: dataRequest.keyOfHealthDepartment,
keyReferenz: dataRequest.keyReferenz,
});
return {
healthDepartment: dataRequest.healthDepartment,
start: dataRequest.start,
end: dataRequest.end,
requestDetails: dataRequest.requestDetails,
};
}

async sendContactsEvents(code: IrisCode, data: IrisContactsEvents, user: IrisUserInfo): Promise<void> {
if (!this.codeKeyMap.has(code)) {
throw new Error("Code could not be found in key map. Did you perform 'getDataRequest' before?");
}
const keys = this.codeKeyMap.get(code);
const { dataToTransport, keyToTransport } = encryptData(keys.keyOfHealthDepartment, data);
const response = await this.axiosInstance.post(`/data-submissions/${code}/contacts_events`, {
checkCode: [ getNameCheckHash(user.firstName, user.lastName), getBirthDateCheckHash(user.birthDate) ].filter(c => !!c),
secret: keyToTransport,
keyReferenz: keys.keyReferenz,
encryptedData: dataToTransport,
} as IrisContactsEventsSubmissionDTO);
if (response.status !== 200) {
console.error('IRIS Gateway responded the following data', response.data);
throw new Error(`Request failed with status Code ${response.status}`);
}
}
}
17 changes: 17 additions & 0 deletions src/lib/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as crypto from 'crypto';

export function encryptData(keyOfHealthDepartment: string, data): { dataToTransport: string; keyToTransport: string } {
const publicKey = crypto.createPublicKey(keyOfHealthDepartment);
const iv = crypto.randomBytes(16);
const key = crypto.randomBytes(32);
const cipher = crypto.createCipheriv('aes-256', key, iv);
const encryptedData = Buffer.concat([cipher.update(JSON.stringify(data), 'utf8'), cipher.final()]);
const encryptedKey = crypto.publicEncrypt(
{ key: publicKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha3' },
key,
);
return {
dataToTransport: encryptedData.toString('base64'),
keyToTransport: encryptedKey.toString('base64'),
};
}
2 changes: 2 additions & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Iris from './Iris';
export default Iris;
14 changes: 14 additions & 0 deletions src/lib/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as crypto from 'crypto';

export function getNameCheckHash(firstName: string, lastName: string): string {
const str = `${firstName.trim()}${lastName.trim()}`.toLowerCase().replace(/\W/g, '');
return crypto.createHash('md5').update(str).digest('base64');
}
export function getBirthDateCheckHash(birthDate?: string): string | undefined {
if (!birthDate) {
return undefined;
}
const date = new Date(birthDate);
const str = `${date.getFullYear()}${(date.getMonth() + 1).toString().padStart(2, '0')}${date.getDate().toString().padStart(2, '0')}`
return crypto.createHash('md5').update(str).digest('base64');
}
2 changes: 2 additions & 0 deletions src/types/IrisCode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
type IrisCode = string;
export default IrisCode;
5 changes: 5 additions & 0 deletions src/types/IrisCodeKeyMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import IrisDataRequestDTO from './dto/IrisDataRequestDTO';
import IrisCode from './IrisCode';

type IrisCodeKeyMap = Map<IrisCode, Pick<IrisDataRequestDTO, 'keyOfHealthDepartment' | 'keyReferenz'>>;
export default IrisCodeKeyMap;
72 changes: 72 additions & 0 deletions src/types/IrisContactsEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
interface DataProvider {
firstName?: string;
lastName?: string;
dateOfBirth?: string;
}
enum Sex {
MALE = 'MALE',
FEMALE = 'FEMALE',
OTHER = 'OTHER',
UNKNOWN = 'UNKNOWN',
}
interface Address {
street: string;
houseNumber: string;
zipCode: string;
city: string;
}

enum ContactCategory {
HIGH_RISK = 'HIGH_RISK',
HIGH_RISK_MED = 'HIGH_RISK_MED',
MEDIUM_RISK_MED = 'MEDIUM_RISK_MED',
LOW_RISK = 'LOW_RISK',
NO_RISK = 'NO_RISK',
}
interface ContactPerson {
firstName: string;
lastName: string;
dateOfBirth?: string;
sex?: Sex;
email?: string;
phone?: string;
mobilPhone?: string;
address?: Address;
workPlace?: {
name?: string;
pointOfContact?: string;
phone?: string;
address?: Address;
};
contactInformation?: {
date?: string;
contactCategory?: ContactCategory;
basicConditions?: string;
};
}

interface Event {
name?: string;
phone?: string;
address?: Address;
additionalInformation?: string;
}

interface ContactPersonList {
contactPersons: Array<ContactPerson>;
dataProvider?: DataProvider;
startDate?: string;
endDate?: string;
}

interface EventList {
events: Array<Event>;
dataProvider?: DataProvider;
startDate?: string;
endDate?: string;
}

export default interface IrisContactsEvents {
contacts: ContactPersonList;
events: EventList;
}
4 changes: 4 additions & 0 deletions src/types/IrisDataRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import IrisDataRequestDTO from './dto/IrisDataRequestDTO';

type IrisDataRequest = Pick<IrisDataRequestDTO, 'healthDepartment' | 'start' | 'end' | 'requestDetails'>
export default IrisDataRequest
3 changes: 3 additions & 0 deletions src/types/IrisOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default interface IrisOptions {
baseUrl: string;
}
5 changes: 5 additions & 0 deletions src/types/IrisUserInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default interface IrisUserInfo {
firstName: string;
lastName: string;
birthDate?: string;
}
6 changes: 6 additions & 0 deletions src/types/dto/IrisContactsEventsSubmissionDTO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default interface IrisContactsEventsSubmissionDTO {
checkCode: Array<string>;
secret: string;
keyReferenz: string;
encryptedData: string;
}
9 changes: 9 additions & 0 deletions src/types/dto/IrisDataRequestDTO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default interface IrisDataRequestDTO {
healthDepartment: string;
keyOfHealthDepartment: string;
keyReferenz: string;
start?: string;
end?: string;
requestDetails?: string;
}

17 changes: 17 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,11 @@
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256"
integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==

"@types/node@^14.14.35":
version "14.14.35"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.35.tgz#42c953a4e2b18ab931f72477e7012172f4ffa313"
integrity sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag==

"@types/normalize-package-data@^2.4.0":
version "2.4.0"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
Expand Down Expand Up @@ -1214,6 +1219,13 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==

axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
follow-redirects "^1.10.0"

babel-jest@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.9.0.tgz#3fc327cb8467b89d14d7bc70e315104a783ccd54"
Expand Down Expand Up @@ -2842,6 +2854,11 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"

follow-redirects@^1.10.0:
version "1.13.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==

for-each@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
Expand Down

0 comments on commit b330d53

Please sign in to comment.