fhirsmith 0.18.2 → 1.0.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/ACCESSIBILITY.md +119 -0
- package/CHANGELOG.md +149 -0
- package/CONFIGURATION.md +305 -0
- package/LICENSE +29 -0
- package/README.md +29 -122
- package/cluster.js +1220 -0
- package/cluster.md +707 -0
- package/config-template.json +77 -8
- package/extension-tracker/extension-tracker-template.html +5 -8
- package/extension-tracker/extension-tracker.js +0 -2
- package/folder/folder.js +7 -6
- package/ips/analysis.js +560 -0
- package/ips/ips-template.html +5 -8
- package/ips/ips.js +132 -59
- package/ips/nationality.js +120 -0
- package/ips/pipeline.js +74 -21
- package/ips/presenter.js +1 -1
- package/ips/readme.md +132 -25
- package/ips/render.js +466 -77
- package/ips/store.js +79 -22
- package/ips/structure.js +247 -0
- package/ips/terminology.js +1 -1
- package/library/body-limit.js +22 -0
- package/library/cache-generation.js +15 -0
- package/library/cgroup-memory.js +133 -1
- package/library/cluster-counts.js +121 -0
- package/library/cluster-events.js +153 -0
- package/library/cluster-metrics.js +193 -0
- package/library/cluster-status.js +468 -0
- package/library/config-reader.js +96 -0
- package/library/daily-report.js +100 -2
- package/library/html-server.js +27 -1
- package/library/i18nsupport.js +23 -1
- package/library/keep-alive.js +18 -0
- package/library/languages.js +17 -3
- package/library/logger.js +31 -0
- package/library/memory-guard.js +428 -0
- package/library/native-memory.js +52 -0
- package/library/npm-audit.js +51 -25
- package/library/openapi-doc.js +7 -5
- package/library/package-manager.js +8 -0
- package/library/role.js +197 -0
- package/library/server-credentials.js +169 -0
- package/library/sqlite-utilities.js +59 -0
- package/library/ssrf-guard.js +100 -0
- package/library/static-response-stats.js +16 -0
- package/library/version-utilities.js +50 -0
- package/library/worker-count.js +192 -0
- package/nginx.md +30 -11
- package/npmprojector/indexer.js +18 -10
- package/package.json +11 -8
- package/packages/feed-check-page.js +117 -0
- package/packages/feed-check.js +279 -0
- package/packages/npm-check.js +57 -3
- package/packages/npm-signing.js +219 -0
- package/packages/openapi.yaml +207 -1
- package/packages/package-crawler.js +322 -41
- package/packages/packages-template.html +6 -8
- package/packages/packages.js +604 -57
- package/packages/readme.md +113 -1
- package/publisher/publisher-template.html +2 -5
- package/publisher/publisher.js +121 -110
- package/publisher/readme.md +75 -0
- package/registry/crawler.js +84 -108
- package/registry/documentation/configuration.md +34 -0
- package/registry/registry-template.html +5 -8
- package/registry/registry.js +5 -4
- package/root-bare-template.html +7 -4
- package/root-template.html +5 -8
- package/server.js +398 -198
- package/shl/shl.js +38 -37
- package/static/a11y/a11y-check.js +832 -0
- package/static/a11y/axe.min.js +12 -0
- package/static/assets/css/bootstrap-fhir.css +15 -15
- package/static/assets/images/flags/au.svg +18 -0
- package/static/assets/images/flags/unknown.svg +11 -0
- package/static/assets/images/flags/us.svg +63 -0
- package/static/assets/images/icon_audit.png +0 -0
- package/static/assets/images/icon_clinical.png +0 -0
- package/static/assets/images/icon_document.png +0 -0
- package/static/assets/images/icon_external.png +0 -0
- package/static/assets/images/icon_medication.png +0 -0
- package/static/assets/images/icon_organization.png +0 -0
- package/static/assets/images/icon_other.png +0 -0
- package/static/assets/images/icon_person.png +0 -0
- package/static/assets/images/icon_section.png +0 -0
- package/stats-db.js +45 -0
- package/stats.js +197 -10
- package/testing/testing-template.html +5 -8
- package/token/nginx_passport_setup.md +1 -1
- package/token/security_guide.md +1 -1
- package/token/token-template.html +2 -5
- package/token/token.js +40 -36
- package/translations/Messages.properties +19 -0
- package/tx/README.md +154 -4
- package/tx/closure/closure-store.js +126 -3
- package/tx/cm/cm-database.js +69 -57
- package/tx/cm/cm-package.js +29 -1
- package/tx/cs/cs-api.js +284 -19
- package/tx/cs/cs-api.md +41 -4
- package/tx/cs/cs-areacode.js +1 -1
- package/tx/cs/cs-country.js +17 -5
- package/tx/cs/cs-cpt.js +33 -16
- package/tx/cs/cs-cs.js +148 -14
- package/tx/cs/cs-currency.js +1 -1
- package/tx/cs/cs-icd11.js +21 -13
- package/tx/cs/cs-icf.js +22 -3
- package/tx/cs/cs-lang.js +2 -2
- package/tx/cs/cs-loinc.js +185 -66
- package/tx/cs/cs-mimetypes.js +12 -2
- package/tx/cs/cs-ndc.js +40 -25
- package/tx/cs/cs-omop.js +54 -29
- package/tx/cs/cs-rxnorm.js +300 -196
- package/tx/cs/cs-snomed.js +608 -64
- package/tx/cs/cs-ucum.js +14 -4
- package/tx/cs/cs-unii.js +18 -14
- package/tx/cs/cs-virtual.js +210 -0
- package/tx/ecosystem-resolver.js +413 -0
- package/tx/html/cluster-dash-metrics.liquid +26 -0
- package/tx/html/cluster-home-metrics.liquid +31 -0
- package/tx/html/cluster-metrics-script.liquid +251 -0
- package/tx/html/codesystem-operations.liquid +3 -3
- package/tx/html/ecl-panel.liquid +1 -1
- package/tx/html/external-metrics.liquid +17 -0
- package/tx/html/operations-form.liquid +17 -17
- package/tx/html/search-form.liquid +11 -11
- package/tx/html/status-line.liquid +58 -0
- package/tx/html/status-panel.liquid +140 -0
- package/tx/html/status-tasks.liquid +25 -0
- package/tx/html/tx-template.html +2 -5
- package/tx/importers/atc-to-fhir.js +17 -16
- package/tx/importers/export-icd11-codesystem.js +3 -2
- package/tx/importers/import-icd11.js +4 -3
- package/tx/importers/import-loinc.module.js +118 -56
- package/tx/importers/import-ndc.module.js +56 -44
- package/tx/importers/import-rxnorm.module.js +118 -79
- package/tx/importers/import-sct.module.js +57 -58
- package/tx/importers/import-unii.module.js +37 -36
- package/tx/importers/subset-loinc.module.js +49 -48
- package/tx/importers/subset-rxnorm.module.js +29 -28
- package/tx/importers/terminal.js +18 -0
- package/tx/importers/tx-import-base.js +13 -12
- package/tx/importers/tx-import-settings.js +5 -4
- package/tx/importers/tx-import.js +53 -52
- package/tx/library/canonical-resource.js +49 -0
- package/tx/library/codesystem.js +94 -5
- package/tx/library/designations.js +45 -2
- package/tx/library/extensions.js +13 -7
- package/tx/library/implicit-valuesets.js +66 -0
- package/tx/library/renderer.js +77 -21
- package/tx/library/sqlite-utilities.js +2 -0
- package/tx/library/ucum-parsers.js +11 -1
- package/tx/library/wait-for-cache.js +43 -0
- package/tx/library.js +321 -22
- package/tx/openapi-operations.js +6 -4
- package/tx/operation-context.js +460 -99
- package/tx/params.js +25 -0
- package/tx/problems.js +3 -5
- package/tx/provider.js +278 -42
- package/tx/resource-cache-sqlite.js +1050 -0
- package/tx/sct/ecl.js +47 -6
- package/tx/sct/editions.js +39 -0
- package/tx/sct/expressions.js +10 -1
- package/tx/sct/load-config.js +107 -0
- package/tx/sct/mmap.js +109 -0
- package/tx/sct/structures.js +166 -13
- package/tx/sct/text-index.js +5 -1
- package/tx/syndication/syndication-feed.js +2 -2
- package/tx/tx-html.js +44 -8
- package/tx/tx.fhir.org.yml +8 -8
- package/tx/tx.js +282 -80
- package/tx/usage-tracker.js +39 -11
- package/tx/vs/vs-api.js +19 -0
- package/tx/vs/vs-database.js +386 -62
- package/tx/vs/vs-package.js +27 -4
- package/tx/vs/vs-vsac.js +648 -100
- package/tx/workers/batch-validate.js +37 -10
- package/tx/workers/batch.js +6 -1
- package/tx/workers/cache-control.js +15 -4
- package/tx/workers/closure.js +156 -13
- package/tx/workers/compare.js +8 -5
- package/tx/workers/expand.js +1173 -289
- package/tx/workers/metadata.js +35 -0
- package/tx/workers/search.js +53 -1
- package/tx/workers/supplement-filters.js +145 -0
- package/tx/workers/translate.js +169 -4
- package/tx/workers/validate.js +223 -119
- package/tx/workers/worker.js +123 -31
- package/tx/xversion/xv-valueset.js +7 -4
- package/utilities/submit-test-report.js +3 -1
- package/utilities/tx-stress-test.js +482 -0
- package/vcl/readme.md +37 -0
- package/vcl/vcl-parser.js +15 -0
- package/xig/readme.md +52 -0
- package/xig/xig-template.html +5 -8
- package/xig/xig.js +238 -422
- package/changelog-template.md +0 -22
- package/registry/test-runner.js +0 -237
- package/release.sh +0 -80
- package/test-scripts/repro-re2-wasm-leak.js +0 -34
- package/tsconfig.eslint.json +0 -9
- package/tx/html/dash-metrics.liquid +0 -147
- package/tx/html/home-metrics.liquid +0 -225
package/registry/crawler.js
CHANGED
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
// Crawler for gathering server information from terminology servers
|
|
3
3
|
|
|
4
4
|
const axios = require('axios');
|
|
5
|
-
const dns = require('dns');
|
|
6
|
-
const http = require('http');
|
|
7
|
-
const https = require('https');
|
|
8
|
-
const ipaddr = require('ipaddr.js');
|
|
9
5
|
const {
|
|
10
6
|
ServerRegistries,
|
|
11
7
|
ServerRegistry,
|
|
@@ -13,24 +9,11 @@ const {
|
|
|
13
9
|
ServerVersionInformation,
|
|
14
10
|
} = require('./model');
|
|
15
11
|
const { UptimeHistory } = require('./uptime');
|
|
12
|
+
const { ServerCredentials } = require('../library/server-credentials');
|
|
13
|
+
const { createGuardedAgents } = require('../library/ssrf-guard');
|
|
16
14
|
const {Extensions} = require("../tx/library/extensions");
|
|
17
15
|
const {debugLog} = require("../tx/operation-context");
|
|
18
16
|
|
|
19
|
-
// True if an IP literal is anything other than a normal public (unicast) address:
|
|
20
|
-
// loopback, private, link-local (incl. 169.254.169.254 cloud metadata), unique-local,
|
|
21
|
-
// CGNAT, multicast, reserved, etc. IPv4-mapped IPv6 addresses are unwrapped first.
|
|
22
|
-
function isNonPublicAddress(ip) {
|
|
23
|
-
try {
|
|
24
|
-
let addr = ipaddr.parse(ip);
|
|
25
|
-
if (addr.kind() === 'ipv6' && addr.isIPv4MappedAddress()) {
|
|
26
|
-
addr = addr.toIPv4Address();
|
|
27
|
-
}
|
|
28
|
-
return addr.range() !== 'unicast';
|
|
29
|
-
} catch (e) {
|
|
30
|
-
return true; // unparseable - treat as unsafe
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
17
|
const MASTER_URL = 'https://fhir.github.io/ig-registry/tx-servers.json';
|
|
35
18
|
|
|
36
19
|
class RegistryCrawler {
|
|
@@ -42,7 +25,10 @@ class RegistryCrawler {
|
|
|
42
25
|
masterUrl: config.masterUrl || MASTER_URL,
|
|
43
26
|
userAgent: config.userAgent || 'HealthIntersections/FhirServer',
|
|
44
27
|
crawlInterval: config.crawlInterval || 5 * 60 * 1000, // 5 minutes default
|
|
45
|
-
apiKeys: config.apiKeys || {}, //
|
|
28
|
+
apiKeys: config.apiKeys || {}, // server code -> API key (sent as Api-Key)
|
|
29
|
+
// server code -> { tokenEndpoint, clientId, clientSecret, scope }: an OAuth2
|
|
30
|
+
// client_credentials grant; the token is sent as Authorization: Bearer
|
|
31
|
+
oauth: config.oauth || {},
|
|
46
32
|
allowPrivateAddresses: config.allowPrivateAddresses || false, // SSRF opt-out for local test servers
|
|
47
33
|
// the most a server's response can be, once unzipped: the servers are other people's
|
|
48
34
|
maxResponseBytes: (config.maxResponseMB || 100) * 1024 * 1024,
|
|
@@ -61,65 +47,30 @@ class RegistryCrawler {
|
|
|
61
47
|
this.totalBytes = 0;
|
|
62
48
|
this.log = console;
|
|
63
49
|
this.abortController = null;
|
|
50
|
+
// API keys and OAuth tokens for the servers that need them (shared with the tx module's
|
|
51
|
+
// ecosystem resolution: library/server-credentials.js)
|
|
52
|
+
this.credentials = new ServerCredentials({
|
|
53
|
+
apiKeys: this.config.apiKeys,
|
|
54
|
+
oauth: this.config.oauth,
|
|
55
|
+
timeout: this.config.timeout,
|
|
56
|
+
userAgent: this.config.userAgent,
|
|
57
|
+
agents: () => this.guardedAgents(),
|
|
58
|
+
signal: () => this.abortController?.signal,
|
|
59
|
+
log: (level, message) => this.addLogEntry(level, message)
|
|
60
|
+
});
|
|
64
61
|
}
|
|
65
62
|
|
|
66
63
|
useLog(logv) {
|
|
67
64
|
this.log = logv;
|
|
68
65
|
}
|
|
69
66
|
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
// Set config.allowPrivateAddresses = true to disable (e.g. for local test servers).
|
|
75
|
-
ssrfLookup() {
|
|
76
|
-
const allowPrivate = !!(this.config && this.config.allowPrivateAddresses);
|
|
77
|
-
return (hostname, options, callback) => {
|
|
78
|
-
if (typeof options === 'function') {
|
|
79
|
-
callback = options;
|
|
80
|
-
options = {};
|
|
81
|
-
}
|
|
82
|
-
const wantAll = !!(options && options.all);
|
|
83
|
-
dns.lookup(hostname, Object.assign({}, options, { all: true }), (err, addresses) => {
|
|
84
|
-
if (err) {
|
|
85
|
-
callback(err);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (!allowPrivate) {
|
|
89
|
-
for (const a of addresses) {
|
|
90
|
-
if (isNonPublicAddress(a.address)) {
|
|
91
|
-
callback(new Error('Blocked request to non-public address ' + a.address + ' (host ' + hostname + ')'));
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (wantAll) {
|
|
97
|
-
callback(null, addresses);
|
|
98
|
-
} else {
|
|
99
|
-
callback(null, addresses[0].address, addresses[0].family);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// http/https agents that route every connection through the SSRF lookup.
|
|
67
|
+
// http/https agents that only connect to public addresses: the SSRF guard for outbound
|
|
68
|
+
// fetches (library/ssrf-guard.js). The registry is crawled from server-supplied "next" links,
|
|
69
|
+
// so the target host is not trusted. Set config.allowPrivateAddresses = true to disable (e.g.
|
|
70
|
+
// for local test servers).
|
|
106
71
|
guardedAgents() {
|
|
107
72
|
if (!this._guardedAgents) {
|
|
108
|
-
|
|
109
|
-
class GuardedHttpAgent extends http.Agent {
|
|
110
|
-
createConnection(options, cb) {
|
|
111
|
-
return super.createConnection(Object.assign({}, options, { lookup }), cb);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
class GuardedHttpsAgent extends https.Agent {
|
|
115
|
-
createConnection(options, cb) {
|
|
116
|
-
return super.createConnection(Object.assign({}, options, { lookup }), cb);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
this._guardedAgents = {
|
|
120
|
-
httpAgent: new GuardedHttpAgent({ keepAlive: true }),
|
|
121
|
-
httpsAgent: new GuardedHttpsAgent({ keepAlive: true })
|
|
122
|
-
};
|
|
73
|
+
this._guardedAgents = createGuardedAgents({ allowPrivate: !!this.config.allowPrivateAddresses });
|
|
123
74
|
}
|
|
124
75
|
return this._guardedAgents;
|
|
125
76
|
}
|
|
@@ -302,7 +253,7 @@ class RegistryCrawler {
|
|
|
302
253
|
const version = new ServerVersionInformation();
|
|
303
254
|
version.version = versionConfig.version;
|
|
304
255
|
version.address = versionConfig.url;
|
|
305
|
-
version.security = this.getApiKey(server.code) == null ? "open" : "api-key";
|
|
256
|
+
version.security = this.getOAuthConfig(server.code) ? "oauth" : this.getApiKey(server.code) == null ? "open" : "api-key";
|
|
306
257
|
|
|
307
258
|
if (!version.address) {
|
|
308
259
|
this.addLogEntry('error', `No URL for version ${version.version} of ${server.name}`, server.address);
|
|
@@ -338,7 +289,7 @@ class RegistryCrawler {
|
|
|
338
289
|
version.lastTat = `${Date.now() - startTime}ms`;
|
|
339
290
|
this.uptime.record(version.address, true, this.scanTime || startTime);
|
|
340
291
|
|
|
341
|
-
this.addLogEntry('info', ` Server ${version.address}: ${version.lastTat} for ${version.codeSystems.length} CodeSystems and ${version.valueSets.length} ValueSets`);
|
|
292
|
+
this.addLogEntry('info', ` Server ${version.address}: ${version.lastTat} for ${version.codeSystems.length} CodeSystems and ${version.valueSets.length} ValueSets (${version.security})`);
|
|
342
293
|
|
|
343
294
|
} catch (error) {
|
|
344
295
|
debugLog(error);
|
|
@@ -415,7 +366,7 @@ class RegistryCrawler {
|
|
|
415
366
|
async processServerVersionR3(version, server, exclusions) {
|
|
416
367
|
// Get capability statement
|
|
417
368
|
const capabilityUrl = `${version.address}/metadata`;
|
|
418
|
-
const capability = await this.fetchJson(capabilityUrl, server.
|
|
369
|
+
const capability = await this.fetchJson(capabilityUrl, server.code, version.address);
|
|
419
370
|
|
|
420
371
|
version.version = capability.fhirVersion || '3.0.2';
|
|
421
372
|
version.software = capability.software ? capability.software.name : "unknown";
|
|
@@ -425,7 +376,7 @@ class RegistryCrawler {
|
|
|
425
376
|
// Get terminology capabilities (R3 uses Parameters resource)
|
|
426
377
|
try {
|
|
427
378
|
const termCapUrl = `${version.address}/metadata?mode=terminology`;
|
|
428
|
-
const termCap = await this.fetchJson(termCapUrl, server.
|
|
379
|
+
const termCap = await this.fetchJson(termCapUrl, server.code, version.address);
|
|
429
380
|
|
|
430
381
|
if (termCap.parameter) {
|
|
431
382
|
termCap.parameter.forEach(param => {
|
|
@@ -461,7 +412,7 @@ class RegistryCrawler {
|
|
|
461
412
|
async processServerVersionR4or5(version, server, defVersion, exclusions) {
|
|
462
413
|
// Get capability statement
|
|
463
414
|
const capabilityUrl = `${version.address}/metadata`;
|
|
464
|
-
const capability = await this.fetchJson(capabilityUrl, server.code);
|
|
415
|
+
const capability = await this.fetchJson(capabilityUrl, server.code, version.address);
|
|
465
416
|
|
|
466
417
|
version.version = capability.fhirVersion || defVersion;
|
|
467
418
|
version.software = capability.software ? capability.software.name : "unknown";
|
|
@@ -473,7 +424,7 @@ class RegistryCrawler {
|
|
|
473
424
|
// Get terminology capabilities
|
|
474
425
|
try {
|
|
475
426
|
const termCapUrl = `${version.address}/metadata?mode=terminology`;
|
|
476
|
-
const termCap = await this.fetchJson(termCapUrl, server.code);
|
|
427
|
+
const termCap = await this.fetchJson(termCapUrl, server.code, version.address);
|
|
477
428
|
|
|
478
429
|
if (termCap.codeSystem) {
|
|
479
430
|
termCap.codeSystem.forEach(cs => {
|
|
@@ -530,7 +481,7 @@ class RegistryCrawler {
|
|
|
530
481
|
|
|
531
482
|
if (this.abortController?.signal.aborted) break;
|
|
532
483
|
this.log.debug(`Fetching value sets from ${searchUrl}`);
|
|
533
|
-
const bundle = await this.fetchJson(searchUrl, server.code);
|
|
484
|
+
const bundle = await this.fetchJson(searchUrl, server.code, version.address);
|
|
534
485
|
|
|
535
486
|
// Process entries in this page
|
|
536
487
|
if (bundle.entry) {
|
|
@@ -598,36 +549,27 @@ class RegistryCrawler {
|
|
|
598
549
|
|
|
599
550
|
/**
|
|
600
551
|
* Fetch JSON from a URL
|
|
552
|
+
* @param {string} url
|
|
553
|
+
* @param {string} serverCode - the code the server's credentials (apiKeys, oauth) are under
|
|
554
|
+
* @param {string} [credentialBase] - the server's own address. When given, credentials are
|
|
555
|
+
* sent only to that origin: next links come from the server, and may point anywhere
|
|
601
556
|
*/
|
|
602
|
-
async fetchJson(url,
|
|
557
|
+
async fetchJson(url, serverCode, credentialBase) {
|
|
603
558
|
try {
|
|
604
559
|
// Add timestamp to bypass cache
|
|
605
560
|
const fetchUrl = url.includes('?')
|
|
606
561
|
? `${url}&_ts=${Date.now()}`
|
|
607
562
|
: `${url}?_ts=${Date.now()}`;
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
if (apiKey) {
|
|
617
|
-
headers['Api-Key'] = apiKey;
|
|
563
|
+
|
|
564
|
+
const sendCredentials = !credentialBase || this.sameOrigin(url, credentialBase);
|
|
565
|
+
const useOAuth = sendCredentials && !!this.getOAuthConfig(serverCode);
|
|
566
|
+
|
|
567
|
+
let response = await this.getJson(fetchUrl, await this.credentialHeaders(serverCode, sendCredentials, false));
|
|
568
|
+
if (response.status === 401 && useOAuth) {
|
|
569
|
+
// the token was revoked or expired early: get a new one, and try once more
|
|
570
|
+
response = await this.getJson(fetchUrl, await this.credentialHeaders(serverCode, sendCredentials, true));
|
|
618
571
|
}
|
|
619
|
-
|
|
620
|
-
const response = await axios.get(fetchUrl, {
|
|
621
|
-
timeout: this.config.timeout,
|
|
622
|
-
headers: headers,
|
|
623
|
-
signal: this.abortController?.signal,
|
|
624
|
-
validateStatus: (status) => status < 500, // Don't throw on 4xx
|
|
625
|
-
// counted as it is unzipped, so a small response that unzips to a lot is stopped too
|
|
626
|
-
maxContentLength: this.config.maxResponseBytes,
|
|
627
|
-
httpAgent: this.guardedAgents().httpAgent,
|
|
628
|
-
httpsAgent: this.guardedAgents().httpsAgent
|
|
629
|
-
});
|
|
630
|
-
|
|
572
|
+
|
|
631
573
|
if (response.status >= 400) {
|
|
632
574
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
633
575
|
}
|
|
@@ -657,16 +599,50 @@ class RegistryCrawler {
|
|
|
657
599
|
}
|
|
658
600
|
}
|
|
659
601
|
|
|
602
|
+
async getJson(fetchUrl, credentials) {
|
|
603
|
+
return axios.get(fetchUrl, {
|
|
604
|
+
timeout: this.config.timeout,
|
|
605
|
+
headers: Object.assign({
|
|
606
|
+
'Accept': 'application/json, application/fhir+json',
|
|
607
|
+
'User-Agent': this.config.userAgent
|
|
608
|
+
}, credentials),
|
|
609
|
+
signal: this.abortController?.signal,
|
|
610
|
+
validateStatus: (status) => status < 500, // Don't throw on 4xx
|
|
611
|
+
// counted as it is unzipped, so a small response that unzips to a lot is stopped too
|
|
612
|
+
maxContentLength: this.config.maxResponseBytes,
|
|
613
|
+
httpAgent: this.guardedAgents().httpAgent,
|
|
614
|
+
httpsAgent: this.guardedAgents().httpsAgent
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
sameOrigin(url, base) {
|
|
619
|
+
try {
|
|
620
|
+
return new URL(url).origin === new URL(base).origin;
|
|
621
|
+
} catch (e) {
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
660
626
|
/**
|
|
661
|
-
*
|
|
627
|
+
* The headers that carry a server's credentials (see ServerCredentials)
|
|
662
628
|
*/
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
return this.config.apiKeys[name];
|
|
629
|
+
async credentialHeaders(serverCode, sendCredentials, renewToken) {
|
|
630
|
+
if (!sendCredentials) {
|
|
631
|
+
return {};
|
|
667
632
|
}
|
|
668
|
-
|
|
669
|
-
|
|
633
|
+
return this.credentials.headers(serverCode, renewToken);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
getApiKey(name) {
|
|
637
|
+
return this.credentials.getApiKey(name);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
getOAuthConfig(name) {
|
|
641
|
+
return this.credentials.getOAuthConfig(name);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
async getAccessToken(serverCode, renew = false) {
|
|
645
|
+
return this.credentials.getAccessToken(serverCode, renew);
|
|
670
646
|
}
|
|
671
647
|
|
|
672
648
|
/**
|
|
@@ -12,6 +12,7 @@ The registry is configured under `modules.registry` in the server's configuratio
|
|
|
12
12
|
"timeout": 30000,
|
|
13
13
|
"userAgent": "YourServer/1.0",
|
|
14
14
|
"apiKeys": {},
|
|
15
|
+
"oauth": {},
|
|
15
16
|
"maxResponseMB": 100,
|
|
16
17
|
"uptimeWindowDays": 30,
|
|
17
18
|
"uptimeRetentionDays": 90,
|
|
@@ -25,6 +26,39 @@ The registry is configured under `modules.registry` in the server's configuratio
|
|
|
25
26
|
|---|---|---|
|
|
26
27
|
| `masterUrl` | HL7's `tx-servers.json` | The ecosystem's master registration file ([readme.md](readme.md#the-specifications)). Point this at your own file to run a registry for your own ecosystem. |
|
|
27
28
|
| `apiKeys` | `{}` | API keys for servers the registry needs one to scan, by server code (as in the server's registration). Sent as an `Api-Key` header. An endpoint scanned with a key is reported as `security: api-key` in the API. |
|
|
29
|
+
| `oauth` | `{}` | OAuth2 client credentials for servers the registry needs a token to scan, by server code - see [below](#oauth). An endpoint scanned with a token is reported as `security: oauth` in the API. |
|
|
30
|
+
|
|
31
|
+
Credentials are only sent to the origin (scheme, host and port) of the server's registered
|
|
32
|
+
address. A `next` link that points somewhere else is still followed, but without them.
|
|
33
|
+
|
|
34
|
+
### OAuth
|
|
35
|
+
|
|
36
|
+
For a server that issues tokens by the OAuth2 `client_credentials` grant, the registry asks
|
|
37
|
+
the token endpoint for a token, sending its client id and secret in the form body
|
|
38
|
+
(`client_secret_post`), and sends the token as `Authorization: Bearer`. The token is reused
|
|
39
|
+
until it is about to expire (`expires_in`; 5 minutes if the endpoint doesn't say), and
|
|
40
|
+
replaced early if the server answers 401. A server with an `oauth` entry is not sent its
|
|
41
|
+
`apiKeys` entry.
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
"oauth": {
|
|
45
|
+
"nzhts": {
|
|
46
|
+
"tokenEndpoint": "https://authenticate.nzhts.digital.health.nz/auth/realms/nzhts/protocol/openid-connect/token",
|
|
47
|
+
"clientId": "hl7",
|
|
48
|
+
"clientSecret": "...",
|
|
49
|
+
"scope": ""
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
| Property | |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `tokenEndpoint` | The token endpoint. Must be `https:`. Redirects are not followed. |
|
|
57
|
+
| `clientId`, `clientSecret` | The client's credentials, from the server's operator. |
|
|
58
|
+
| `scope` | Optional. Sent if present and not empty. |
|
|
59
|
+
|
|
60
|
+
The field names are those of the HL7 Java tooling's `oauthClientCredentials` settings, so the
|
|
61
|
+
same values can be copied between the two.
|
|
28
62
|
|
|
29
63
|
## Scanning
|
|
30
64
|
|
|
@@ -55,8 +55,6 @@
|
|
|
55
55
|
<div class="container"> <!-- container -->
|
|
56
56
|
<div style="background-color: #ad1f2f; padding: 6px; color: white;"> <!-- container -->
|
|
57
57
|
|
|
58
|
-
<a href="http://www.hl7.org/fhir" style="color: gold" title="Fast Healthcare Interoperability Resources - Home Page"><img border="0" src="/icon-fhir-16.png" style="vertical-align: text-bottom"/> <b>FHIR</b></a> © HL7.org |
|
|
59
|
-
<a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] |
|
|
60
58
|
<a href="/" style="color: gold"> Server Home</a> |
|
|
61
59
|
<a href="/tx-reg" style="color: gold">Registry Home</a> |
|
|
62
60
|
<a href="/tx-reg/resolve" style="color: gold">Resolve</a> |
|
|
@@ -75,7 +73,7 @@
|
|
|
75
73
|
<div class="inner-wrapper">
|
|
76
74
|
<div class="col-9">
|
|
77
75
|
|
|
78
|
-
<h2><img border="0" src="
|
|
76
|
+
<h2><a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" title="FHIRsmith [%ver%]"><img border="0" src="/FHIRsmith32.png" alt="FHIRsmith Logo" style="vertical-align: text-bottom"/></a> [%title%] </h2>
|
|
79
77
|
|
|
80
78
|
[%content%]
|
|
81
79
|
|
|
@@ -93,8 +91,7 @@
|
|
|
93
91
|
<div class="container"> <!-- container -->
|
|
94
92
|
<div class="inner-wrapper">
|
|
95
93
|
<p>
|
|
96
|
-
<a href="
|
|
97
|
-
<a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] © HealthIntersections.com.au 2023+ |
|
|
94
|
+
<a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" alt="FHIRsmith Logo" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] © HealthIntersections.com.au 2023+ |
|
|
98
95
|
Terminology Registry [%crawler-status%] |
|
|
99
96
|
([%ms%] ms)
|
|
100
97
|
[%sponsorMessage%]
|
|
@@ -118,10 +115,10 @@
|
|
|
118
115
|
================================================== -->
|
|
119
116
|
<!-- Placed at the end of the document so the pages load faster -->
|
|
120
117
|
<!-- <script src="/assets/js/jquery.js"/> -->
|
|
121
|
-
<script src="/assets/js/bootstrap.min.js"
|
|
122
|
-
<script src="/assets/js/respond.min.js"
|
|
118
|
+
<script src="/assets/js/bootstrap.min.js"></script>
|
|
119
|
+
<script src="/assets/js/respond.min.js"></script>
|
|
123
120
|
|
|
124
|
-
<script src="/assets/js/fhir.js"
|
|
121
|
+
<script src="/assets/js/fhir.js"></script>
|
|
125
122
|
|
|
126
123
|
<!-- Analytics Below
|
|
127
124
|
================================================== -->
|
package/registry/registry.js
CHANGED
|
@@ -91,6 +91,7 @@ class RegistryModule {
|
|
|
91
91
|
timeout: config.timeout || 30000,
|
|
92
92
|
userAgent: config.userAgent || 'FHIRRegistryServer/1.0',
|
|
93
93
|
apiKeys: config.apiKeys || {},
|
|
94
|
+
oauth: config.oauth || {},
|
|
94
95
|
maxResponseMB: config.maxResponseMB,
|
|
95
96
|
uptimeWindowDays: config.uptimeWindowDays,
|
|
96
97
|
uptimeRetentionDays: config.uptimeRetentionDays
|
|
@@ -1559,7 +1560,7 @@ class RegistryModule {
|
|
|
1559
1560
|
// Query information section
|
|
1560
1561
|
html += '<div class="card mb-4">';
|
|
1561
1562
|
html += '<div class="card-header">';
|
|
1562
|
-
html += '<
|
|
1563
|
+
html += '<h3 class="card-title">Query Information</h3>';
|
|
1563
1564
|
html += '</div>';
|
|
1564
1565
|
html += '<div class="card-body">';
|
|
1565
1566
|
html += `<p><strong>FHIR Version:</strong> ${escape(fhirVersion)}</p>`;
|
|
@@ -1577,7 +1578,7 @@ class RegistryModule {
|
|
|
1577
1578
|
// Authoritative servers section
|
|
1578
1579
|
html += '<div class="card mb-4">';
|
|
1579
1580
|
html += '<div class="card-header">';
|
|
1580
|
-
html += '<
|
|
1581
|
+
html += '<h3 class="card-title">Authoritative Servers</h3>';
|
|
1581
1582
|
html += '</div>';
|
|
1582
1583
|
html += '<div class="card-body">';
|
|
1583
1584
|
|
|
@@ -1616,7 +1617,7 @@ class RegistryModule {
|
|
|
1616
1617
|
// Candidate servers section
|
|
1617
1618
|
html += '<div class="card mb-4">';
|
|
1618
1619
|
html += '<div class="card-header">';
|
|
1619
|
-
html += '<
|
|
1620
|
+
html += '<h3 class="card-title">Candidate Servers</h3>';
|
|
1620
1621
|
html += '</div>';
|
|
1621
1622
|
html += '<div class="card-body">';
|
|
1622
1623
|
|
|
@@ -1867,7 +1868,7 @@ class RegistryModule {
|
|
|
1867
1868
|
case 'error': levelStyle = 'color: #d9534f; font-weight: bold;'; break;
|
|
1868
1869
|
case 'warn': levelStyle = 'color: #f0ad4e;'; break;
|
|
1869
1870
|
case 'debug': levelStyle = 'color: #5cb85c;'; break;
|
|
1870
|
-
default: levelStyle = 'color: #
|
|
1871
|
+
default: levelStyle = 'color: #0056b3;'; // info
|
|
1871
1872
|
}
|
|
1872
1873
|
|
|
1873
1874
|
// Format: [time] [LEVEL] message
|
package/root-bare-template.html
CHANGED
|
@@ -71,9 +71,12 @@
|
|
|
71
71
|
|
|
72
72
|
<body style="margin-left: 10px">
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
<!-- the page's one h1, the size of an h2 as before -->
|
|
75
|
+
<h1 style="font-size:1.5em;margin:0.83em 0">[%title%] </h1>
|
|
75
76
|
|
|
77
|
+
<main>
|
|
76
78
|
[%content%]
|
|
79
|
+
</main>
|
|
77
80
|
|
|
78
81
|
|
|
79
82
|
<!-- JS and analytics only. -->
|
|
@@ -81,10 +84,10 @@
|
|
|
81
84
|
================================================== -->
|
|
82
85
|
<!-- Placed at the end of the document so the pages load faster -->
|
|
83
86
|
<!-- <script src="/assets/js/jquery.js"/> -->
|
|
84
|
-
<script src="/assets/js/bootstrap.min.js"
|
|
85
|
-
<script src="/assets/js/respond.min.js"
|
|
87
|
+
<script src="/assets/js/bootstrap.min.js"></script>
|
|
88
|
+
<script src="/assets/js/respond.min.js"></script>
|
|
86
89
|
|
|
87
|
-
<script src="/assets/js/fhir.js"
|
|
90
|
+
<script src="/assets/js/fhir.js"></script>
|
|
88
91
|
|
|
89
92
|
<!-- Analytics Below
|
|
90
93
|
================================================== -->
|
package/root-template.html
CHANGED
|
@@ -53,8 +53,6 @@
|
|
|
53
53
|
<div id="stripe"> </div>
|
|
54
54
|
<div class="container"> <!-- container -->
|
|
55
55
|
<div style="background-color: #ad1f2f; padding: 6px; color: white;"> <!-- container -->
|
|
56
|
-
<a href="http://www.hl7.org/fhir" style="color: gold" title="Fast Healthcare Interoperability Resources - Home Page"><img border="0" src="/icon-fhir-16.png" style="vertical-align: text-bottom"/> <b>FHIR</b></a> © HL7.org |
|
|
57
|
-
<a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] |
|
|
58
56
|
<a href="/" style="color: gold"> Server Home</a>
|
|
59
57
|
|
|
60
58
|
|
|
@@ -70,7 +68,7 @@
|
|
|
70
68
|
<div class="inner-wrapper">
|
|
71
69
|
<div class="col-9">
|
|
72
70
|
|
|
73
|
-
|
|
71
|
+
<h2><a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" title="FHIRsmith [%ver%]"><img border="0" src="/FHIRsmith32.png" alt="FHIRsmith Logo" style="vertical-align: text-bottom"/></a> FHIRsmith: [%title%] </h2>
|
|
74
72
|
|
|
75
73
|
[%content%]
|
|
76
74
|
|
|
@@ -89,8 +87,7 @@
|
|
|
89
87
|
<div class="container"> <!-- container -->
|
|
90
88
|
<div class="inner-wrapper">
|
|
91
89
|
<p>
|
|
92
|
-
<a href="
|
|
93
|
-
<a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] © HealthIntersections.com.au 2023+ |
|
|
90
|
+
<a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" alt="FHIRsmith Logo" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] © HealthIntersections.com.au 2023+ |
|
|
94
91
|
([%ms%] ms)
|
|
95
92
|
[%sponsorMessage%]
|
|
96
93
|
</p>
|
|
@@ -113,10 +110,10 @@
|
|
|
113
110
|
================================================== -->
|
|
114
111
|
<!-- Placed at the end of the document so the pages load faster -->
|
|
115
112
|
<!-- <script src="/assets/js/jquery.js"/> -->
|
|
116
|
-
<script src="/assets/js/bootstrap.min.js"
|
|
117
|
-
<script src="/assets/js/respond.min.js"
|
|
113
|
+
<script src="/assets/js/bootstrap.min.js"></script>
|
|
114
|
+
<script src="/assets/js/respond.min.js"></script>
|
|
118
115
|
|
|
119
|
-
<script src="/assets/js/fhir.js"
|
|
116
|
+
<script src="/assets/js/fhir.js"></script>
|
|
120
117
|
|
|
121
118
|
<!-- Analytics Below
|
|
122
119
|
================================================== -->
|