rxome-generator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +484 -0
- package/demos/demo_data_full.json +149 -0
- package/demos/demo_data_full_compressed.json +76 -0
- package/demos/demo_from_gui.json +64 -0
- package/demos/rxome.decrypt.json +1 -0
- package/demos/rxome.encrypt.json +1 -0
- package/demos/testmessage_cipher +19 -0
- package/index.js +3 -0
- package/lib/phenopackets.json +1948 -0
- package/lib/rxome-api-demo.js +56 -0
- package/lib/rxome-api.js +119 -0
- package/lib/rxome-api.test.js +22 -0
- package/lib/rxome-generator.js +349 -0
- package/lib/rxome-generator.test.js +379 -0
- package/package.json +50 -0
- package/rxcode +401 -0
- package/rxcode.test.js +104 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const CRYPT_PRIVATE_KEY = `
|
|
2
|
+
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
|
3
|
+
|
|
4
|
+
xYYEY0gdfxYJKwYBBAHaRw8BAQdAYqNejQxT4gE+w2nwBvP+pe19P152F6LV
|
|
5
|
+
8sqM/Qhut2D+CQMIlk6wdgn1LOXgt14o00FVGL49l+pQB8umf27PPnWrJ9IS
|
|
6
|
+
IElQjaCsRYA3ZD/rnDUZiBGVS9++PaegYL339QT2bDp8l6VVtvcxG77svZ2n
|
|
7
|
+
a80Xcnh0ZXN0IDxpbmZvQHJ4b21lLm5ldD7CjAQQFgoAHQUCY0gdfwQLCQcI
|
|
8
|
+
AxUICgQWAAIBAhkBAhsDAh4BACEJEGvtGF4xeK/wFiEEP1IE7WS9w5iQJRD8
|
|
9
|
+
a+0YXjF4r/CW7AD7BlIn1BHx8kOdyrt6E0L1EKIUi88Q3jQghmvlQomsIzIB
|
|
10
|
+
AKW3e7gYkQJufFTlTWmD5dYmP4v3DfAGvkmFljOvHfwGx4sEY0gdfxIKKwYB
|
|
11
|
+
BAGXVQEFAQEHQNBoBiWwb9t6WCMulp6/opgVJ88iKOY9MpAoZ5dyEbJwAwEI
|
|
12
|
+
B/4JAwhZhPonkWKqteBKH35kf07JpJVMX8LWmZCqdFqXw8tmsU81LtCxVRl8
|
|
13
|
+
exJ0vJor/6LmBUnzMrVSG3S0PCVLw0hAfCH4nN9HxT5gEc1mFFfSwngEGBYI
|
|
14
|
+
AAkFAmNIHX8CGwwAIQkQa+0YXjF4r/AWIQQ/UgTtZL3DmJAlEPxr7RheMXiv
|
|
15
|
+
8FlgAP9S+Oc82N6iSA4gj9hOt6dz7E4YE/3XGf+7uVQb3xVYSQD6A4X+cisS
|
|
16
|
+
fqCVd5bPCMqAQQHjHgGBQawPK/PXyk9JxQQ=
|
|
17
|
+
=idJc
|
|
18
|
+
-----END PGP PRIVATE KEY BLOCK-----
|
|
19
|
+
`
|
|
20
|
+
|
|
21
|
+
const CRYPT_PUBLIC_KEY = `
|
|
22
|
+
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
23
|
+
|
|
24
|
+
xjMEY0gdfxYJKwYBBAHaRw8BAQdAYqNejQxT4gE+w2nwBvP+pe19P152F6LV
|
|
25
|
+
8sqM/Qhut2DNF3J4dGVzdCA8aW5mb0ByeG9tZS5uZXQ+wowEEBYKAB0FAmNI
|
|
26
|
+
HX8ECwkHCAMVCAoEFgACAQIZAQIbAwIeAQAhCRBr7RheMXiv8BYhBD9SBO1k
|
|
27
|
+
vcOYkCUQ/GvtGF4xeK/wluwA+wZSJ9QR8fJDncq7ehNC9RCiFIvPEN40IIZr
|
|
28
|
+
5UKJrCMyAQClt3u4GJECbnxU5U1pg+XWJj+L9w3wBr5JhZYzrx38Bs44BGNI
|
|
29
|
+
HX8SCisGAQQBl1UBBQEBB0DQaAYlsG/belgjLpaev6KYFSfPIijmPTKQKGeX
|
|
30
|
+
chGycAMBCAfCeAQYFggACQUCY0gdfwIbDAAhCRBr7RheMXiv8BYhBD9SBO1k
|
|
31
|
+
vcOYkCUQ/GvtGF4xeK/wWWAA/1L45zzY3qJIDiCP2E63p3PsThgT/dcZ/7u5
|
|
32
|
+
VBvfFVhJAPoDhf5yKxJ+oJV3ls8IyoBBAeMeAYFBrA8r89fKT0nFBA==
|
|
33
|
+
=odkw
|
|
34
|
+
-----END PGP PUBLIC KEY BLOCK-----
|
|
35
|
+
`
|
|
36
|
+
|
|
37
|
+
const R_ID = 'rxome';
|
|
38
|
+
const R_PRIVATE_KEY = 'NamaTB+xwDFxtkQyBBkjRr5GEaXNtCw/G4qydnhQk5Y=';
|
|
39
|
+
const R_PUBLIC_KEY = 'XvbhLWKbA1wfKsx3B7FKQuDQsZTZ/dMXWiD1MehBxZg=';
|
|
40
|
+
|
|
41
|
+
const J_ID = 'rxomej'
|
|
42
|
+
const J_PRIVATE_KEY = 'QhcoRruGBVP39XCh8BujCE+q42qCRy/tu2CQ4YmRBgg=';
|
|
43
|
+
const J_PUBLIC_KEY = 'XL/i8jrJC55AdOV3zYHIIa095De5eYbDqWDPDW2r8tk=';
|
|
44
|
+
|
|
45
|
+
const DEMO_CREDENTIALS = {
|
|
46
|
+
id: R_ID,
|
|
47
|
+
user: 'test@rxome.net',
|
|
48
|
+
key: R_PRIVATE_KEY
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
exports.CRYPT_PRIVATE_KEY = CRYPT_PRIVATE_KEY;
|
|
52
|
+
exports.CRYPT_PUBLIC_KEY = CRYPT_PUBLIC_KEY;
|
|
53
|
+
exports.DEMO_API_PRIVATE_KEY = R_PRIVATE_KEY;
|
|
54
|
+
exports.DEMO_API_PUBLIC_KEY = R_PUBLIC_KEY;
|
|
55
|
+
exports.DEMO_API_ID = R_ID;
|
|
56
|
+
exports.DEMO_CREDENTIALS = DEMO_CREDENTIALS;
|
package/lib/rxome-api.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const Axios = require( 'axios' );
|
|
2
|
+
const ED = require( 'noble-ed25519' );
|
|
3
|
+
const Protobuf = require('protobufjs');
|
|
4
|
+
//const BASE64 = require('@protobufjs/base64')
|
|
5
|
+
|
|
6
|
+
const API = 'https://www.rxome.net';
|
|
7
|
+
const TESTAPI = 'https://testapi.gene-talk.de';
|
|
8
|
+
const APIENTRY = 'api/v1';
|
|
9
|
+
const VSTR = 'API1.0'
|
|
10
|
+
exports.API = API;
|
|
11
|
+
exports.TESTAPI = TESTAPI;
|
|
12
|
+
exports.APIENTRY = APIENTRY;
|
|
13
|
+
exports.VSTR = VSTR;
|
|
14
|
+
|
|
15
|
+
/************************************************************************************
|
|
16
|
+
* Helper functions
|
|
17
|
+
************************************************************************************/
|
|
18
|
+
|
|
19
|
+
// have issues:
|
|
20
|
+
const base64ToBufferVintage = data => atob(data.toString());
|
|
21
|
+
//const bufferToBase64 = data => Buffer.from(data).toString('base64');
|
|
22
|
+
|
|
23
|
+
const unpack = (arr) => Uint8Array.from( arr.map( c => c.charCodeAt(0) ));
|
|
24
|
+
|
|
25
|
+
let readSigKey = name => { console.log("Error: not supported") }
|
|
26
|
+
|
|
27
|
+
const base64ToBuffer = enc => {
|
|
28
|
+
const len = Protobuf.util.base64.length(enc);
|
|
29
|
+
let buf = new Protobuf.util.Array( len );
|
|
30
|
+
Protobuf.util.base64.decode(enc, buf, 0);
|
|
31
|
+
return buf;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const bufferToBase64 = buf => {
|
|
35
|
+
return Protobuf.util.base64.encode(buf, 0, buf.length);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
exports.base64ToBuffer = base64ToBuffer;
|
|
39
|
+
exports.bufferToBase64 = bufferToBase64;
|
|
40
|
+
exports.unpack = unpack;
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
/************************************************************************************
|
|
44
|
+
* Functions for node.js only
|
|
45
|
+
************************************************************************************/
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const FS = require( 'fs' );
|
|
49
|
+
|
|
50
|
+
readSigKey = name => {
|
|
51
|
+
if (!FS.existsSync( name )) {
|
|
52
|
+
throw 'Key file not found!';
|
|
53
|
+
}
|
|
54
|
+
return FS.readFileSync( name ).slice(0,44);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
exports.generateApiKeys = async (name = 'rxome', dir = '.') => {
|
|
58
|
+
const privateKey = ED.utils.randomPrivateKey();
|
|
59
|
+
const publicKey = await ED.getPublicKey(privateKey);
|
|
60
|
+
await Promise.all([
|
|
61
|
+
FS.writeFile( `${dir}/${name}.private.apikey`, bufferToBase64( privateKey ), { mode: 0o600 }, err => { if (err) throw err; } ),
|
|
62
|
+
FS.writeFile( `${dir}/${name}.public.apikey`, bufferToBase64( publicKey ), { mode: 0o600 }, err => { if (err) throw err; } )
|
|
63
|
+
]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
if (e instanceof Error && e.code !== "MODULE_NOT_FOUND") {
|
|
69
|
+
throw e;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/************************************************************************************/
|
|
74
|
+
|
|
75
|
+
exports.signData = async( keyId, user, keyB64, created, debug = false ) => {
|
|
76
|
+
const message = `x-date: ${created}\nx-rxome-user: ${user}`
|
|
77
|
+
const messageUi8 = unpack( Array.from(message) );
|
|
78
|
+
|
|
79
|
+
const key = unpack( [...base64ToBufferVintage(keyB64)] );
|
|
80
|
+
debug && console.log('Base 64 key: ', keyB64);
|
|
81
|
+
debug && console.log('Binary key: ', key, " Key length: ", key.length);
|
|
82
|
+
|
|
83
|
+
const signature = await ED.sign( messageUi8, key);
|
|
84
|
+
const sigB64 = bufferToBase64( signature );
|
|
85
|
+
const auth=`Signature keyId=\"${keyId}\",algorithm=\"ed25519\",headers=\"x-date x-rxome-user\",signature=\"${sigB64}\",created=\"${created}\"`
|
|
86
|
+
debug && console.log('Auth string: ', auth);
|
|
87
|
+
|
|
88
|
+
return auth;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
exports.fetchData = async ( url, credentials, pseudonym = '', debug = false ) => {
|
|
93
|
+
debug && console.log( 'Fetching from', url )
|
|
94
|
+
|
|
95
|
+
const created = Date.now();
|
|
96
|
+
const keyId = credentials.keyId || "rxome";
|
|
97
|
+
const user = credentials.user || `${keyId}@rxome.net`;
|
|
98
|
+
const keyB64 = credentials.key || readSigKey( process.cwd()+'/'+credentials.keyFile );
|
|
99
|
+
|
|
100
|
+
auth = await exports.signData( keyId, user, keyB64, created, debug );
|
|
101
|
+
|
|
102
|
+
return Axios({
|
|
103
|
+
url: url,
|
|
104
|
+
method: 'GET',
|
|
105
|
+
params: {
|
|
106
|
+
pslab: !!pseudonym.trim()
|
|
107
|
+
},
|
|
108
|
+
headers: {
|
|
109
|
+
Authorization: auth,
|
|
110
|
+
'x-date': created,
|
|
111
|
+
'x-rxome-user': user
|
|
112
|
+
},
|
|
113
|
+
timeout: 5000
|
|
114
|
+
})
|
|
115
|
+
.then(res => {
|
|
116
|
+
debug && console.log( "Result Data= ", res.data )
|
|
117
|
+
return res.data
|
|
118
|
+
})
|
|
119
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const RxAPI = require('./rxome-api');
|
|
2
|
+
const FS = require('fs');
|
|
3
|
+
const ED = require( 'noble-ed25519' );
|
|
4
|
+
|
|
5
|
+
describe('API access', () => {
|
|
6
|
+
test.skip('generates valid API access keys', async () => {
|
|
7
|
+
await RxAPI.generateApiKeys( 'jesttest' );
|
|
8
|
+
expect( FS.existsSync('jesttest.private.apikey') ).toBe( true );
|
|
9
|
+
expect( FS.existsSync('jesttest.public.apikey') ).toBe( true );
|
|
10
|
+
expect( FS.statSync('jesttest.private.apikey').size - 44 ).toBeLessThan( 2 );
|
|
11
|
+
expect( FS.statSync('jesttest.public.apikey').size - 44 ).toBeLessThan( 2 );
|
|
12
|
+
|
|
13
|
+
const message='Answer to life the universe and everything';
|
|
14
|
+
const messageUi8 = RxAPI.unpack(Array.from(message));
|
|
15
|
+
const privKey = RxAPI.unpack([...RxAPI.base64ToBuffer( FS.readFileSync('jesttest.private.apikey'))])
|
|
16
|
+
const pubKey = RxAPI.unpack([...RxAPI.base64ToBuffer( FS.readFileSync('jesttest.public.apikey'))])
|
|
17
|
+
const signature = await ED.sign(messageUi8, privKey);
|
|
18
|
+
const isValid = await ED.verify(signature, messageUi8, pubKey);
|
|
19
|
+
expect( isValid ).toBeTruthy;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
});
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// https://openpgpjs.org/
|
|
2
|
+
const PGP = require( 'openpgp' );
|
|
3
|
+
|
|
4
|
+
// https://github.com/soldair/node-qrcode
|
|
5
|
+
const QRCode = require( 'qrcode' );
|
|
6
|
+
|
|
7
|
+
const jsonKeyConverter = require('json-key-converter');
|
|
8
|
+
const Protobuf = require('protobufjs');
|
|
9
|
+
const PhenoPacketDescriptor = require("./phenopackets.json");
|
|
10
|
+
|
|
11
|
+
//const { constants } = require('buffer');
|
|
12
|
+
|
|
13
|
+
const RxAPI= require('./rxome-api');
|
|
14
|
+
|
|
15
|
+
const apiVer = '1.0';
|
|
16
|
+
const RXAPI = RxAPI.API;
|
|
17
|
+
const RXTESTAPI = RxAPI.TESTAPI;
|
|
18
|
+
const APIENTRY = RxAPI.APIENTRY;
|
|
19
|
+
|
|
20
|
+
const PASSPHRASE = 'n0thin6r3@11ym@773r5';
|
|
21
|
+
|
|
22
|
+
// const ProtoBufRoot = await Protobuf.load('assets/scripts/modules/phenopackets/schema/v2/phenopackets.proto');
|
|
23
|
+
const ProtoBufRoot = Protobuf.Root.fromJSON( PhenoPacketDescriptor );
|
|
24
|
+
const PhenoPacket = ProtoBufRoot.lookupType('org.phenopackets.schema.v2.Phenopacket')
|
|
25
|
+
|
|
26
|
+
const ENUM_SEX = ['UNKNOWN', 'FEMALE', 'MALE', 'OTHER']
|
|
27
|
+
exports.ENUM_SEX = ENUM_SEX;
|
|
28
|
+
|
|
29
|
+
const ENUM_PROG_STATE = ['UNKNOWN_PROGRESS', 'IN_PROGRESS', 'COMPLETED', 'SOLVED', 'UNSOLVED']
|
|
30
|
+
exports.ENUM_PROG_STATE = ENUM_PROG_STATE;
|
|
31
|
+
|
|
32
|
+
const ENUM_ACMG =
|
|
33
|
+
['NOT_PROVIDED', 'BENIGN', 'LIKELY_BENIGN', 'UNCERTAIN_SIGNIFICANCE', 'LIKELY_PATHOGENIC', 'PATHOGENIC'];
|
|
34
|
+
|
|
35
|
+
exports.ENUM_ACMG = ENUM_ACMG;
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
/************************************************************************************
|
|
39
|
+
* Functions for node.js only
|
|
40
|
+
************************************************************************************/
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const FS = require( 'fs/promises' );
|
|
44
|
+
const { version } = require('os');
|
|
45
|
+
|
|
46
|
+
exports.generateRxomeKeyFiles = async (name = 'rxome', dir = '.') => {
|
|
47
|
+
const { privateKey, publicKey } = await exports.generateRxomeKeys( name );
|
|
48
|
+
await Promise.all([
|
|
49
|
+
FS.writeFile(`${dir}/${name}.private.key`, privateKey, { mode: 0o600 }, err => { console.log(err); }),
|
|
50
|
+
FS.writeFile(`${dir}/${name}.public.key`, publicKey, { mode: 0o600 }, function (err) { console.log(err); })
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
exports.writeQR = async ( filename, data, api = RXAPI, apiEntry = APIENTRY ) => {
|
|
55
|
+
const {qrData, pseudonym} = await exports.prepareQR( data, api, apiEntry );
|
|
56
|
+
//const base64Data = qr_code.replace(/^data:image\/png;base64,/, "");
|
|
57
|
+
//FS.writeFile(filename, base64Data, 'base64', (err) => {console.log(err)} );
|
|
58
|
+
QRCode.toFile( filename, qrData, { type: 'png'} )
|
|
59
|
+
return pseudonym
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
catch (e) {
|
|
64
|
+
if (e instanceof Error && e.code !== "MODULE_NOT_FOUND") {
|
|
65
|
+
throw e;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/************************************************************************************/
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
exports.generateRxomeKeys = async (name = 'rxome') => {
|
|
73
|
+
return { privateKey, publicKey, revocationCertificate } = await PGP.generateKey({
|
|
74
|
+
type: 'ecc',
|
|
75
|
+
curve: 'curve25519',
|
|
76
|
+
//type: 'rsa',
|
|
77
|
+
//rsaBits: 2048,
|
|
78
|
+
userIDs: [{ name: name, email: 'info@rxome.net' }],
|
|
79
|
+
passphrase: PASSPHRASE,
|
|
80
|
+
format: 'armored'
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
exports.fetchKey = async ( credentials, pseudonym = '', api = RXAPI, debug = false, apiEntry = APIENTRY ) => {
|
|
86
|
+
const result = await RxAPI.fetchData( `${api}/${apiEntry}/getpseudonym`, credentials, pseudonym, debug )
|
|
87
|
+
if ( !result.pseudonym )
|
|
88
|
+
result.pseudonym = pseudonym;
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
/*
|
|
92
|
+
yields: { key: PGP_key, version: ..., pseudonym: ... }
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
exports.fetchDemoPrivateKey = async ( credentials, api=RXTESTAPI, debug = false, apiEntry = APIENTRY ) =>
|
|
97
|
+
await RxAPI.fetchData( `${api}/${apiEntry}/getprivatedemokey`, credentials, '', debug )
|
|
98
|
+
|
|
99
|
+
/*
|
|
100
|
+
yields: { private_key: PGP_key }
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
exports.fetchRxomeKey = async ( credentials, api = RXAPI, debug = false, apiEntry = APIENTRY ) =>
|
|
104
|
+
await RxAPI.fetchData( `${api}/${apiEntry}/getrxomekey`, credentials, '', debug )
|
|
105
|
+
|
|
106
|
+
/*
|
|
107
|
+
yields: { key: PGP_key, version: ... }
|
|
108
|
+
*/
|
|
109
|
+
exports.convert_to_snake_case = data => jsonKeyConverter.convert( data, { camel: false } );
|
|
110
|
+
|
|
111
|
+
exports.convertToCamelCase = data => jsonKeyConverter.convert( data, { camel: true } );
|
|
112
|
+
|
|
113
|
+
exports.compressPhenoPacket = ( data ) => {
|
|
114
|
+
const compressedData = {
|
|
115
|
+
...data,
|
|
116
|
+
};
|
|
117
|
+
if ( data.phenotypicFeatures ) {
|
|
118
|
+
compressedData.compressedFeatures = {
|
|
119
|
+
includes: data.phenotypicFeatures.filter( feat => feat.excluded === undefined || feat.excluded === false || feat.excluded === 'false' ).map( feat => feat.type.id ),
|
|
120
|
+
excludes: data.phenotypicFeatures.filter( feat => feat.excluded === true || feat.excluded === 'true' ).map( feat => feat.type.id )
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
delete compressedData.phenotypicFeatures;
|
|
124
|
+
return compressedData;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
exports.sanitizeSex = ( str ) =>
|
|
129
|
+
isNaN(+(str.toString()))
|
|
130
|
+
? Math.max(0, ENUM_SEX.indexOf(str.toUpperCase().replace('_SEX', '')))
|
|
131
|
+
: +str;
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
exports.sanitizeProgState = ( str ) =>
|
|
135
|
+
isNaN(+(str.toString()))
|
|
136
|
+
? Math.max(0, ENUM_PROG_STATE.indexOf(str.toUpperCase()))
|
|
137
|
+
: +str;
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
exports.sanitizeACMG = ( str ) =>
|
|
141
|
+
isNaN(+(str.toString()))
|
|
142
|
+
? Math.max(0, ENUM_ACMG.indexOf(str.toUpperCase()))
|
|
143
|
+
: +str
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
exports.whiteListPhenoPacket = ( data ) => {
|
|
147
|
+
res = {};
|
|
148
|
+
data.id && (res.id = data.id);
|
|
149
|
+
data.comment && (res.comment = data.comment);
|
|
150
|
+
data.subject && (res.subject = data.subject);
|
|
151
|
+
data.phenotypicFeatures && (res.phenotypicFeatures = data.phenotypicFeatures);
|
|
152
|
+
data.compressedFeatures && (res.compressedFeatures = data.compressedFeatures);
|
|
153
|
+
data.interpretations && (res.interpretations = data.interpretations);
|
|
154
|
+
data.diagnosis && (res.diagnosis = data.diagnosis);
|
|
155
|
+
data.metaData && (res.metaData = data.metaData);
|
|
156
|
+
data.credentials && (res.credentials = data.credentials);
|
|
157
|
+
return res;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
exports.sanitizePhenoPacket = ( data, key = '' ) => {
|
|
162
|
+
if ( data instanceof Object ) {
|
|
163
|
+
if (Array.isArray( data )) {
|
|
164
|
+
return data.map( x => exports.sanitizePhenoPacket(x));
|
|
165
|
+
} else {
|
|
166
|
+
let obj={};
|
|
167
|
+
for ( let k in data ) {
|
|
168
|
+
obj[k] = exports.sanitizePhenoPacket( data[k], k );
|
|
169
|
+
}
|
|
170
|
+
return obj;
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
switch( key ){
|
|
174
|
+
case 'sex':
|
|
175
|
+
return exports.sanitizeSex( data );
|
|
176
|
+
case 'progressStatus':
|
|
177
|
+
return exports.sanitizeProgState( data );
|
|
178
|
+
case 'acmgPathogenicityClassification':
|
|
179
|
+
return exports.sanitizeACMG( data );
|
|
180
|
+
default:
|
|
181
|
+
return data;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
exports.verifyPhenoPacket = (data) => (
|
|
188
|
+
PhenoPacket.verify( data )
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
exports.encodePhenoPacket = (data) => (
|
|
193
|
+
PhenoPacket.encode( data ).finish()
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
exports.decodePhenoPacket = (data) => (
|
|
198
|
+
PhenoPacket.decode( data )
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
exports.encode_serial = async (publicKeyStr, message) => {
|
|
203
|
+
const publicKey = await PGP.readKey({ armoredKey: publicKeyStr });
|
|
204
|
+
const encrypted = await PGP.encrypt({
|
|
205
|
+
message: await PGP.createMessage({ text: message }),
|
|
206
|
+
encryptionKeys: publicKey,
|
|
207
|
+
//format: 'binary',
|
|
208
|
+
//signingKeys: privateKey
|
|
209
|
+
});
|
|
210
|
+
return encrypted;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
exports.encode = async (publicKeyStr, message, binary = false) => {
|
|
215
|
+
return await Promise.all([
|
|
216
|
+
PGP.readKey({ armoredKey: publicKeyStr }),
|
|
217
|
+
PGP.createMessage( (binary
|
|
218
|
+
? { binary: message, format: 'binary' }
|
|
219
|
+
: { text: message }) )
|
|
220
|
+
]).then( async ([publicKey, message]) => {
|
|
221
|
+
const encrypted = await PGP.encrypt({
|
|
222
|
+
message: message,
|
|
223
|
+
encryptionKeys: publicKey,
|
|
224
|
+
format: (binary ? 'binary' : 'armored'),
|
|
225
|
+
// signingKeys: privateKey
|
|
226
|
+
});
|
|
227
|
+
return encrypted;
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
exports.decode_serial = async (privateKeyStr, encrypted) => {
|
|
234
|
+
const privateKey = await PGP.readPrivateKey( { armoredKey: privateKeyStr } );
|
|
235
|
+
const privateKeyDecr = await PGP.decryptKey({
|
|
236
|
+
privateKey: privateKey,
|
|
237
|
+
passphrase: PASSPHRASE
|
|
238
|
+
});
|
|
239
|
+
const message = await PGP.readMessage({
|
|
240
|
+
armoredMessage: encrypted
|
|
241
|
+
});
|
|
242
|
+
const { data: decrypted, signatures } = await PGP.decrypt({
|
|
243
|
+
message,
|
|
244
|
+
decryptionKeys: privateKeyDecr
|
|
245
|
+
});
|
|
246
|
+
return decrypted;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
exports.decode = async (privateKeyStr, encrypted, binary = false) => {
|
|
251
|
+
return await Promise.all([
|
|
252
|
+
PGP.readPrivateKey({ armoredKey: privateKeyStr })
|
|
253
|
+
.then( (privateKey) => {
|
|
254
|
+
return PGP.decryptKey({
|
|
255
|
+
privateKey: privateKey,
|
|
256
|
+
passphrase: PASSPHRASE
|
|
257
|
+
})
|
|
258
|
+
}),
|
|
259
|
+
PGP.readMessage( (binary
|
|
260
|
+
? {binaryMessage: encrypted}
|
|
261
|
+
: {armoredMessage: encrypted} )
|
|
262
|
+
)
|
|
263
|
+
]).then( async ([ privateKeyDecr, message]) => {
|
|
264
|
+
const { data: decrypted, signatures } = await PGP.decrypt({
|
|
265
|
+
message,
|
|
266
|
+
//verificationKeys: publicKey, // optional
|
|
267
|
+
decryptionKeys: privateKeyDecr,
|
|
268
|
+
format: (binary ? 'binary' : 'utf8')
|
|
269
|
+
});
|
|
270
|
+
// check signature validity (signed messages only)
|
|
271
|
+
// try {
|
|
272
|
+
// await signatures[0].verified; // throws on invalid signature
|
|
273
|
+
// console.log('Signature is valid');
|
|
274
|
+
// } catch (e) {
|
|
275
|
+
// throw new Error('Signature could not be verified: ' + e.message);
|
|
276
|
+
// }
|
|
277
|
+
return decrypted;
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
exports.prepareQR = async ( data, api = RXAPI, apiEntry = APIENTRY ) => {
|
|
283
|
+
const { metaData, credentials, ...medical } = data;
|
|
284
|
+
|
|
285
|
+
const whiteListMedical = exports.whiteListPhenoPacket( medical );
|
|
286
|
+
const sanitizedMedical = exports.sanitizePhenoPacket( whiteListMedical );
|
|
287
|
+
const compressedMedical = exports.compressPhenoPacket( sanitizedMedical );
|
|
288
|
+
const protobufMedical = exports.encodePhenoPacket( compressedMedical );
|
|
289
|
+
const base64Medical = RxAPI.bufferToBase64(protobufMedical);
|
|
290
|
+
|
|
291
|
+
const key = await this.fetchKey( credentials, metaData.pseudonym || '', api, false, apiEntry );
|
|
292
|
+
|
|
293
|
+
// check:
|
|
294
|
+
const buff = RxAPI.base64ToBuffer( base64Medical );
|
|
295
|
+
const pheno = exports.decodePhenoPacket( buff );
|
|
296
|
+
//const medicalDeciphered = JSON.parse( JSON.stringify( pheno ));
|
|
297
|
+
//console.log( JSON.stringify(medicalDeciphered,' ', 2 ))
|
|
298
|
+
|
|
299
|
+
const cipher = await exports.encode( key.key, base64Medical );
|
|
300
|
+
//console.log( 'Cipher:', JSON.stringify(cipher) );
|
|
301
|
+
|
|
302
|
+
//delete metaData.pseudonym;
|
|
303
|
+
const newMetaData = Object.fromEntries(
|
|
304
|
+
Object.entries( metaData ).filter( ([key, val]) => key !== 'pseudonym')
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
const qrData = {
|
|
308
|
+
...newMetaData,
|
|
309
|
+
keyver: key.version,
|
|
310
|
+
apiver: apiVer,
|
|
311
|
+
pseudonym: key.pseudonym,
|
|
312
|
+
payload: cipher.toString()
|
|
313
|
+
}
|
|
314
|
+
// console.log( qrData );
|
|
315
|
+
// console.log( "QR-Data " , JSON.stringify( qrData ).length );
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
qrData: JSON.stringify( qrData ),
|
|
319
|
+
pseudonym: key.pseudonym
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
exports.makeQR = async ( data, api = RXAPI, apiEntry = APIENTRY ) => {
|
|
325
|
+
const {qrData, pseudonym} = await exports.prepareQR( data, api, apiEntry );
|
|
326
|
+
return {
|
|
327
|
+
qr_code: await QRCode.toDataURL( qrData ),
|
|
328
|
+
pseudonym: pseudonym,
|
|
329
|
+
qr_data: qrData
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
exports.versionStr = () => {
|
|
334
|
+
return 'RxOME QR Generator 1.0.0, 2022 Tom Kamphans, GeneTalk GmbH'
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
// convert .proto to .json:
|
|
339
|
+
// cd assets/scripts/modules/phenopackets/schema/v2
|
|
340
|
+
// ../../../../../../node_modules/protobufjs-cli/bin/pbjs -t json phenopackets.proto > phenopackets.json
|
|
341
|
+
|
|
342
|
+
//decode:
|
|
343
|
+
// /////
|
|
344
|
+
// const clearBufferBin = await Coder.decode(PRIVATE_KEY, '', cipherBin, true );
|
|
345
|
+
// const clearBin = Buffer.from (clearBufferBin, 'Binary');
|
|
346
|
+
// const phenoPrimeBin = Coder.decodePhenoPacket(clearBin);
|
|
347
|
+
// const phenoBin = JSON.parse(JSON.stringify(phenoPrimeBin));
|
|
348
|
+
// ////
|
|
349
|
+
|