fhirsmith 0.3.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.
Files changed (277) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/FHIRsmith.png +0 -0
  3. package/README.md +277 -0
  4. package/config-template.json +144 -0
  5. package/library/folder-setup.js +58 -0
  6. package/library/html-server.js +166 -0
  7. package/library/html.js +835 -0
  8. package/library/i18nsupport.js +259 -0
  9. package/library/languages.js +779 -0
  10. package/library/logger-telnet.js +205 -0
  11. package/library/logger.js +279 -0
  12. package/library/package-manager.js +876 -0
  13. package/library/utilities.js +196 -0
  14. package/library/version-utilities.js +1056 -0
  15. package/npmprojector/config-example.json +13 -0
  16. package/npmprojector/indexer.js +394 -0
  17. package/npmprojector/npmprojector.js +395 -0
  18. package/npmprojector/readme.md +174 -0
  19. package/npmprojector/watcher.js +335 -0
  20. package/package.json +119 -0
  21. package/packages/package-crawler.js +846 -0
  22. package/packages/packages-template.html +126 -0
  23. package/packages/packages.js +2838 -0
  24. package/passwords.ini +2 -0
  25. package/publisher/publisher-template.html +208 -0
  26. package/publisher/publisher.js +2167 -0
  27. package/publisher/task-draft.js +458 -0
  28. package/registry/api.js +735 -0
  29. package/registry/crawler.js +637 -0
  30. package/registry/model.js +513 -0
  31. package/registry/readme.md +243 -0
  32. package/registry/registry-data.json +121015 -0
  33. package/registry/registry-template.html +126 -0
  34. package/registry/registry.js +1395 -0
  35. package/registry/test-runner.js +237 -0
  36. package/root-template.html +124 -0
  37. package/server.js +524 -0
  38. package/shl/private-key.pem +5 -0
  39. package/shl/public-key.pem +18 -0
  40. package/shl/shl.js +1125 -0
  41. package/shl/vhl.js +69 -0
  42. package/static/FHIRsmith128.png +0 -0
  43. package/static/FHIRsmith16.png +0 -0
  44. package/static/FHIRsmith32.png +0 -0
  45. package/static/FHIRsmith64.png +0 -0
  46. package/static/assets/css/bootstrap-fhir.css +5302 -0
  47. package/static/assets/css/bootstrap-glyphicons.css +2 -0
  48. package/static/assets/css/bootstrap.css +4097 -0
  49. package/static/assets/css/jquery-ui.css +523 -0
  50. package/static/assets/css/jquery-ui.structure.css +863 -0
  51. package/static/assets/css/jquery-ui.structure.min.css +5 -0
  52. package/static/assets/css/jquery-ui.theme.css +439 -0
  53. package/static/assets/css/jquery-ui.theme.min.css +5 -0
  54. package/static/assets/css/jquery.ui.all.css +7 -0
  55. package/static/assets/css/modules.css +18 -0
  56. package/static/assets/css/project.css +367 -0
  57. package/static/assets/css/pygments-manni.css +66 -0
  58. package/static/assets/css/tags.css +74 -0
  59. package/static/assets/css/xml.css +2 -0
  60. package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
  61. package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
  62. package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
  63. package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
  64. package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
  65. package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
  66. package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
  67. package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
  68. package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
  69. package/static/assets/ico/favicon.ico +0 -0
  70. package/static/assets/ico/favicon.png +0 -0
  71. package/static/assets/images/fhir-logo-www.png +0 -0
  72. package/static/assets/images/fhir-logo.png +0 -0
  73. package/static/assets/images/hl7-logo.png +0 -0
  74. package/static/assets/images/logo_ansinew.jpg +0 -0
  75. package/static/assets/images/search.png +0 -0
  76. package/static/assets/images/stripe.png +0 -0
  77. package/static/assets/images/target.png +0 -0
  78. package/static/assets/images/tx-registry-root.gif +0 -0
  79. package/static/assets/images/tx-registry.png +0 -0
  80. package/static/assets/images/tx-server.png +0 -0
  81. package/static/assets/images/tx-version.png +0 -0
  82. package/static/assets/js/bootstrap.min.js +6 -0
  83. package/static/assets/js/fhir-gw.js +259 -0
  84. package/static/assets/js/fhir.js +2 -0
  85. package/static/assets/js/html5shiv.js +8 -0
  86. package/static/assets/js/jcookie.js +96 -0
  87. package/static/assets/js/jquery-ui.min.js +6 -0
  88. package/static/assets/js/jquery.js +10716 -0
  89. package/static/assets/js/jquery.min.js +2 -0
  90. package/static/assets/js/jquery.ui.core.js +314 -0
  91. package/static/assets/js/jquery.ui.draggable.js +825 -0
  92. package/static/assets/js/jquery.ui.mouse.js +162 -0
  93. package/static/assets/js/jquery.ui.resizable.js +842 -0
  94. package/static/assets/js/jquery.ui.widget.js +268 -0
  95. package/static/assets/js/json2.js +487 -0
  96. package/static/assets/js/jtip.js +97 -0
  97. package/static/assets/js/respond.min.js +6 -0
  98. package/static/assets/js/statuspage.js +70 -0
  99. package/static/assets/js/xml.js +2 -0
  100. package/static/dist/js/bootstrap.js +1964 -0
  101. package/static/favicon.png +0 -0
  102. package/static/fhir.css +626 -0
  103. package/static/icon-fhir-16.png +0 -0
  104. package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  105. package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  106. package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
  107. package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  108. package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  109. package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  110. package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  111. package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  112. package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  113. package/static/images/ui-icons_222222_256x240.png +0 -0
  114. package/static/images/ui-icons_228ef1_256x240.png +0 -0
  115. package/static/images/ui-icons_ef8c08_256x240.png +0 -0
  116. package/static/images/ui-icons_ffd27a_256x240.png +0 -0
  117. package/static/images/ui-icons_ffffff_256x240.png +0 -0
  118. package/static/js/jquery.effects.blind.js +49 -0
  119. package/static/js/jquery.effects.bounce.js +78 -0
  120. package/static/js/jquery.effects.clip.js +54 -0
  121. package/static/js/jquery.effects.core.js +763 -0
  122. package/static/js/jquery.effects.drop.js +50 -0
  123. package/static/js/jquery.effects.explode.js +79 -0
  124. package/static/js/jquery.effects.fade.js +32 -0
  125. package/static/js/jquery.effects.fold.js +56 -0
  126. package/static/js/jquery.effects.highlight.js +50 -0
  127. package/static/js/jquery.effects.pulsate.js +51 -0
  128. package/static/js/jquery.effects.scale.js +178 -0
  129. package/static/js/jquery.effects.shake.js +57 -0
  130. package/static/js/jquery.effects.slide.js +50 -0
  131. package/static/js/jquery.effects.transfer.js +45 -0
  132. package/static/js/jquery.ui.accordion.js +611 -0
  133. package/static/js/jquery.ui.autocomplete.js +612 -0
  134. package/static/js/jquery.ui.button.js +416 -0
  135. package/static/js/jquery.ui.datepicker.js +1823 -0
  136. package/static/js/jquery.ui.dialog.js +878 -0
  137. package/static/js/jquery.ui.droppable.js +296 -0
  138. package/static/js/jquery.ui.position.js +252 -0
  139. package/static/js/jquery.ui.progressbar.js +109 -0
  140. package/static/js/jquery.ui.selectable.js +266 -0
  141. package/static/js/jquery.ui.slider.js +666 -0
  142. package/static/js/jquery.ui.sortable.js +1077 -0
  143. package/static/js/jquery.ui.tabs.js +758 -0
  144. package/stats.js +80 -0
  145. package/test-cache/vsac/vsac-valuesets.db +0 -0
  146. package/token/nginx_passport_setup.md +383 -0
  147. package/token/security_guide.md +294 -0
  148. package/token/token-template.html +330 -0
  149. package/token/token.js +1300 -0
  150. package/translations/Messages.properties +1510 -0
  151. package/translations/Messages_ar.properties +1399 -0
  152. package/translations/Messages_de.properties +836 -0
  153. package/translations/Messages_es.properties +737 -0
  154. package/translations/Messages_fr.properties +1 -0
  155. package/translations/Messages_ja.properties +893 -0
  156. package/translations/Messages_nl.properties +1357 -0
  157. package/translations/Messages_pt.properties +1302 -0
  158. package/translations/Messages_ru.properties +1 -0
  159. package/translations/Messages_uz.properties +1 -0
  160. package/translations/Messages_zh.properties +1 -0
  161. package/translations/rendering-phrases.properties +1128 -0
  162. package/translations/rendering-phrases_ar.properties +1091 -0
  163. package/translations/rendering-phrases_de.properties +6 -0
  164. package/translations/rendering-phrases_es.properties +6 -0
  165. package/translations/rendering-phrases_fr.properties +624 -0
  166. package/translations/rendering-phrases_ja.properties +21 -0
  167. package/translations/rendering-phrases_nl.properties +970 -0
  168. package/translations/rendering-phrases_pt.properties +1020 -0
  169. package/translations/rendering-phrases_ru.properties +1094 -0
  170. package/translations/rendering-phrases_uz.properties +1 -0
  171. package/translations/rendering-phrases_zh.properties +1 -0
  172. package/tx/README.md +418 -0
  173. package/tx/cm/cm-api.js +110 -0
  174. package/tx/cm/cm-database.js +735 -0
  175. package/tx/cm/cm-package.js +325 -0
  176. package/tx/cs/cs-api.js +789 -0
  177. package/tx/cs/cs-areacode.js +615 -0
  178. package/tx/cs/cs-country.js +1110 -0
  179. package/tx/cs/cs-cpt.js +785 -0
  180. package/tx/cs/cs-cs.js +1579 -0
  181. package/tx/cs/cs-currency.js +539 -0
  182. package/tx/cs/cs-db.js +1321 -0
  183. package/tx/cs/cs-hgvs.js +329 -0
  184. package/tx/cs/cs-lang.js +465 -0
  185. package/tx/cs/cs-loinc.js +1485 -0
  186. package/tx/cs/cs-mimetypes.js +238 -0
  187. package/tx/cs/cs-ndc.js +704 -0
  188. package/tx/cs/cs-omop.js +1025 -0
  189. package/tx/cs/cs-provider-api.js +43 -0
  190. package/tx/cs/cs-provider-list.js +37 -0
  191. package/tx/cs/cs-rxnorm.js +808 -0
  192. package/tx/cs/cs-snomed.js +1102 -0
  193. package/tx/cs/cs-ucum.js +514 -0
  194. package/tx/cs/cs-unii.js +271 -0
  195. package/tx/cs/cs-uri.js +218 -0
  196. package/tx/cs/cs-usstates.js +305 -0
  197. package/tx/dev.fhir.org.yml +14 -0
  198. package/tx/fixtures/test-cases-setup.json +18 -0
  199. package/tx/fixtures/test-cases.yml +16 -0
  200. package/tx/html/codesystem-operations.liquid +25 -0
  201. package/tx/html/home-metrics.liquid +247 -0
  202. package/tx/html/operations-form.liquid +148 -0
  203. package/tx/html/search-form.liquid +62 -0
  204. package/tx/html/tx-template.html +133 -0
  205. package/tx/html/valueset-operations.liquid +54 -0
  206. package/tx/importers/atc-to-fhir.js +316 -0
  207. package/tx/importers/import-loinc.module.js +1536 -0
  208. package/tx/importers/import-ndc.module.js +1088 -0
  209. package/tx/importers/import-rxnorm.module.js +898 -0
  210. package/tx/importers/import-sct.module.js +2457 -0
  211. package/tx/importers/import-unii.module.js +601 -0
  212. package/tx/importers/readme.md +453 -0
  213. package/tx/importers/subset-loinc.module.js +1081 -0
  214. package/tx/importers/subset-rxnorm.module.js +938 -0
  215. package/tx/importers/tx-import-base.js +351 -0
  216. package/tx/importers/tx-import-settings.js +310 -0
  217. package/tx/importers/tx-import.js +357 -0
  218. package/tx/library/canonical-resource.js +88 -0
  219. package/tx/library/capabilitystatement.js +292 -0
  220. package/tx/library/codesystem.js +774 -0
  221. package/tx/library/conceptmap.js +568 -0
  222. package/tx/library/designations.js +932 -0
  223. package/tx/library/errors.js +77 -0
  224. package/tx/library/extensions.js +117 -0
  225. package/tx/library/namingsystem.js +322 -0
  226. package/tx/library/operation-outcome.js +127 -0
  227. package/tx/library/parameters.js +105 -0
  228. package/tx/library/renderer.js +1559 -0
  229. package/tx/library/terminologycapabilities.js +418 -0
  230. package/tx/library/ucum-parsers.js +1029 -0
  231. package/tx/library/ucum-service.js +370 -0
  232. package/tx/library/ucum-types.js +1099 -0
  233. package/tx/library/valueset.js +543 -0
  234. package/tx/library.js +676 -0
  235. package/tx/ocl/cm-ocl.js +106 -0
  236. package/tx/ocl/cs-ocl.js +39 -0
  237. package/tx/ocl/vs-ocl.js +105 -0
  238. package/tx/operation-context.js +568 -0
  239. package/tx/params.js +613 -0
  240. package/tx/provider.js +403 -0
  241. package/tx/sct/ecl.js +1560 -0
  242. package/tx/sct/expressions.js +2077 -0
  243. package/tx/sct/structures.js +1396 -0
  244. package/tx/tx-html.js +1063 -0
  245. package/tx/tx.fhir.org.yml +39 -0
  246. package/tx/tx.js +927 -0
  247. package/tx/vs/vs-api.js +112 -0
  248. package/tx/vs/vs-database.js +786 -0
  249. package/tx/vs/vs-package.js +358 -0
  250. package/tx/vs/vs-vsac.js +366 -0
  251. package/tx/workers/batch-validate.js +129 -0
  252. package/tx/workers/batch.js +361 -0
  253. package/tx/workers/closure.js +32 -0
  254. package/tx/workers/expand.js +1845 -0
  255. package/tx/workers/lookup.js +407 -0
  256. package/tx/workers/metadata.js +467 -0
  257. package/tx/workers/operations.js +34 -0
  258. package/tx/workers/read.js +164 -0
  259. package/tx/workers/search.js +384 -0
  260. package/tx/workers/subsumes.js +334 -0
  261. package/tx/workers/translate.js +492 -0
  262. package/tx/workers/validate.js +2504 -0
  263. package/tx/workers/worker.js +904 -0
  264. package/tx/xml/capabilitystatement-xml.js +63 -0
  265. package/tx/xml/codesystem-xml.js +62 -0
  266. package/tx/xml/conceptmap-xml.js +65 -0
  267. package/tx/xml/namingsystem-xml.js +65 -0
  268. package/tx/xml/operationoutcome-xml.js +127 -0
  269. package/tx/xml/parameters-xml.js +312 -0
  270. package/tx/xml/terminologycapabilities-xml.js +64 -0
  271. package/tx/xml/valueset-xml.js +64 -0
  272. package/tx/xml/xml-base.js +603 -0
  273. package/vcl/vcl-parser.js +1098 -0
  274. package/vcl/vcl.js +253 -0
  275. package/windows-install.js +19 -0
  276. package/xig/xig-template.html +124 -0
  277. package/xig/xig.js +3049 -0
@@ -0,0 +1,294 @@
1
+ # Passport.js Security Implementation Guide
2
+
3
+ ## Overview
4
+
5
+ This guide covers the security considerations and proper implementation of Passport.js OAuth authentication in the FHIR Token Server.
6
+
7
+ ## Key Security Features Implemented
8
+
9
+ ### 1. **Session Security**
10
+ ```javascript
11
+ // Secure session configuration
12
+ {
13
+ store: SQLiteStore, // Persistent session storage
14
+ secret: crypto.randomBytes(64), // Strong session secret
15
+ name: 'fhir.token.sid', // Custom session name (security through obscurity)
16
+ resave: false, // Don't save unchanged sessions
17
+ saveUninitialized: false, // Don't create sessions until something stored
18
+ rolling: true, // Reset expiration on each request
19
+ cookie: {
20
+ secure: NODE_ENV === 'production', // HTTPS only in production
21
+ httpOnly: true, // Prevent XSS access to cookies
22
+ maxAge: 24 * 60 * 60 * 1000, // 24 hour expiration
23
+ sameSite: 'lax' // CSRF protection
24
+ }
25
+ }
26
+ ```
27
+
28
+ ### 2. **CSRF Protection**
29
+ ```javascript
30
+ // Using Lusca for comprehensive protection
31
+ lusca({
32
+ csrf: true, // CSRF token validation
33
+ csp: { /* Content Security Policy */ },
34
+ xframe: 'SAMEORIGIN', // Clickjacking protection
35
+ hsts: { maxAge: 31536000 }, // Force HTTPS
36
+ xssProtection: true, // XSS header
37
+ nosniff: true // MIME sniffing protection
38
+ })
39
+ ```
40
+
41
+ ### 3. **Rate Limiting**
42
+ ```javascript
43
+ // OAuth authentication rate limiting
44
+ const authLimiter = rateLimit({
45
+ windowMs: 15 * 60 * 1000, // 15 minutes
46
+ max: 5, // 5 attempts per window
47
+ message: 'Too many authentication attempts'
48
+ });
49
+ ```
50
+
51
+ ### 4. **API Key Security**
52
+ ```javascript
53
+ // Secure key generation and storage
54
+ const keyData = crypto.randomBytes(32); // Cryptographically secure
55
+ const keyHash = await bcrypt.hash(fullKey, 12); // Strong bcrypt rounds
56
+ const keyPrefix = 'tk_' + crypto.randomBytes(4).toString('hex'); // Identifiable prefix
57
+ ```
58
+
59
+ ## OAuth Provider Security
60
+
61
+ ### 1. **Google OAuth Configuration**
62
+ ```javascript
63
+ // Recommended Google OAuth settings
64
+ {
65
+ clientID: 'your-google-client-id',
66
+ clientSecret: 'your-google-client-secret',
67
+ callbackURL: 'https://yourdomain.com/token/callback/google', // HTTPS required
68
+ scope: ['openid', 'profile', 'email'], // Minimal required scopes
69
+ prompt: 'select_account' // Force account selection
70
+ }
71
+ ```
72
+
73
+ ### 2. **Facebook OAuth Configuration**
74
+ ```javascript
75
+ {
76
+ clientID: 'your-facebook-app-id',
77
+ clientSecret: 'your-facebook-app-secret',
78
+ callbackURL: 'https://yourdomain.com/token/callback/facebook',
79
+ profileFields: ['id', 'emails', 'name'], // Limit profile data
80
+ enableProof: true // App secret proof for added security
81
+ }
82
+ ```
83
+
84
+ ### 3. **GitHub OAuth Configuration**
85
+ ```javascript
86
+ {
87
+ clientID: 'your-github-client-id',
88
+ clientSecret: 'your-github-client-secret',
89
+ callbackURL: 'https://yourdomain.com/token/callback/github',
90
+ scope: ['user:email'], // Minimal scope for email access
91
+ userAgent: 'YourApp/1.0' // Identify your application
92
+ }
93
+ ```
94
+
95
+ ## Security Audit Trail
96
+
97
+ ### 1. **Security Event Logging**
98
+ All security-relevant events are logged:
99
+ - OAuth login attempts
100
+ - API key creation/deletion
101
+ - Failed authentication attempts
102
+ - Suspicious activity patterns
103
+
104
+ ```javascript
105
+ await this.logSecurityEvent(userId, 'oauth_login', req.ip, req.get('User-Agent'), {
106
+ provider: 'google',
107
+ provider_id: profile.id
108
+ });
109
+ ```
110
+
111
+ ### 2. **Usage Tracking**
112
+ - Daily request counts per API key
113
+ - IP address tracking for usage patterns
114
+ - Anomaly detection capabilities
115
+
116
+ ## Production Deployment Checklist
117
+
118
+ ### 1. **Environment Variables**
119
+ ```bash
120
+ # Required environment variables
121
+ NODE_ENV=production
122
+ SESSION_SECRET=your-super-secret-session-key-64-chars-minimum
123
+ CSRF_SECRET=your-csrf-secret-key
124
+
125
+ # OAuth Provider Credentials
126
+ GOOGLE_CLIENT_ID=your-google-client-id
127
+ GOOGLE_CLIENT_SECRET=your-google-client-secret
128
+ FACEBOOK_CLIENT_ID=your-facebook-app-id
129
+ FACEBOOK_CLIENT_SECRET=your-facebook-app-secret
130
+ GITHUB_CLIENT_ID=your-github-client-id
131
+ GITHUB_CLIENT_SECRET=your-github-client-secret
132
+ ```
133
+
134
+ ### 2. **OAuth Provider Setup**
135
+
136
+ #### Google Cloud Console:
137
+ 1. Go to [Google Cloud Console](https://console.cloud.google.com/)
138
+ 2. Create a new project or select existing
139
+ 3. Enable Google+ API and Google OAuth2 API
140
+ 4. Go to Credentials → Create OAuth 2.0 Client ID
141
+ 5. Set authorized redirect URIs: `https://yourdomain.com/token/auth/google/callback`
142
+ 6. Configure OAuth consent screen with minimal scopes
143
+
144
+ #### Facebook for Developers:
145
+ 1. Go to [Facebook for Developers](https://developers.facebook.com/)
146
+ 2. Create a new app or select existing
147
+ 3. Add Facebook Login product
148
+ 4. Configure Valid OAuth Redirect URIs: `https://yourdomain.com/token/auth/facebook/callback`
149
+ 5. Set app domain and privacy policy URL
150
+ 6. Request only `email` permission
151
+
152
+ #### GitHub OAuth Apps:
153
+ 1. Go to GitHub Settings → Developer settings → OAuth Apps
154
+ 2. Create new OAuth App
155
+ 3. Set Authorization callback URL: `https://yourdomain.com/token/auth/github/callback`
156
+ 4. Configure application name and homepage URL
157
+
158
+ ### 3. **Database Security**
159
+ ```javascript
160
+ // Database connection with security settings
161
+ const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => {
162
+ if (err) throw err;
163
+
164
+ // Enable foreign key constraints
165
+ db.run('PRAGMA foreign_keys = ON');
166
+
167
+ // Set secure database settings
168
+ db.run('PRAGMA journal_mode = WAL'); // Write-Ahead Logging
169
+ db.run('PRAGMA synchronous = NORMAL'); // Balance performance/safety
170
+ db.run('PRAGMA temp_store = MEMORY'); // Store temp data in memory
171
+ });
172
+ ```
173
+
174
+ ### 4. **HTTPS Configuration**
175
+ ```javascript
176
+ // Production server setup with HTTPS
177
+ const https = require('https');
178
+ const fs = require('fs');
179
+
180
+ const options = {
181
+ key: fs.readFileSync('path/to/private-key.pem'),
182
+ cert: fs.readFileSync('path/to/certificate.pem'),
183
+ // Optional: intermediate certificates
184
+ ca: fs.readFileSync('path/to/ca-bundle.pem')
185
+ };
186
+
187
+ https.createServer(options, app).listen(443, () => {
188
+ console.log('HTTPS server running on port 443');
189
+ });
190
+ ```
191
+
192
+ ## Security Best Practices
193
+
194
+ ### 1. **API Key Management**
195
+ - Keys are shown only once during creation
196
+ - Keys are hashed with bcrypt (12 rounds)
197
+ - Key prefixes allow for efficient database lookup
198
+ - Usage tracking for anomaly detection
199
+ - Configurable expiration dates
200
+ - Scope-based permissions
201
+
202
+ ### 2. **User Session Management**
203
+ - Sessions stored in database, not memory
204
+ - Session rotation on privilege change
205
+ - Automatic cleanup of expired sessions
206
+ - Logout destroys both session and cookies
207
+
208
+ ### 3. **OAuth State Management**
209
+ - CSRF protection via state parameter
210
+ - State values are cryptographically random
211
+ - State is validated on callback
212
+ - Short-lived state tokens
213
+
214
+ ### 4. **Input Validation**
215
+ - All inputs sanitized and validated
216
+ - SQL injection prevention via parameterized queries
217
+ - XSS prevention via output encoding
218
+ - Rate limiting on all endpoints
219
+
220
+ ### 5. **Error Handling**
221
+ - Generic error messages to prevent information leakage
222
+ - Detailed logging for administrators
223
+ - Graceful degradation on service failures
224
+ - No sensitive data in error responses
225
+
226
+ ## Monitoring and Alerting
227
+
228
+ ### 1. **Security Events to Monitor**
229
+ - Multiple failed authentication attempts
230
+ - API key creation from new IP addresses
231
+ - Unusual usage patterns
232
+ - Session hijacking attempts
233
+ - OAuth callback failures
234
+
235
+ ### 2. **Log Analysis**
236
+ ```javascript
237
+ // Example log entries to monitor
238
+ {
239
+ "level": "warn",
240
+ "message": "Failed authentication attempt",
241
+ "ip": "192.168.1.100",
242
+ "userAgent": "Mozilla/5.0...",
243
+ "provider": "google",
244
+ "timestamp": "2025-09-14T10:30:00Z"
245
+ }
246
+ ```
247
+
248
+ ## Compliance Considerations
249
+
250
+ ### 1. **GDPR Compliance**
251
+ - User consent for data processing
252
+ - Right to data portability
253
+ - Right to erasure (delete account)
254
+ - Data minimization (only collect necessary data)
255
+ - Privacy by design
256
+
257
+ ### 2. **HIPAA Considerations** (if handling health data)
258
+ - Encryption at rest and in transit
259
+ - Access logging and audit trails
260
+ - User authentication and authorization
261
+ - Business Associate Agreements with OAuth providers
262
+
263
+ ## Testing Security
264
+
265
+ ### 1. **Automated Security Testing**
266
+ ```javascript
267
+ // Example security tests
268
+ describe('OAuth Security', () => {
269
+ test('should reject requests without CSRF token', async () => {
270
+ const response = await request(app)
271
+ .post('/token/keys')
272
+ .send({ name: 'test' });
273
+ expect(response.status).toBe(403);
274
+ });
275
+
276
+ test('should rate limit authentication attempts', async () => {
277
+ // Make 6 failed requests
278
+ for (let i = 0; i < 6; i++) {
279
+ await request(app).get('/token/auth/google');
280
+ }
281
+ const response = await request(app).get('/token/auth/google');
282
+ expect(response.status).toBe(429);
283
+ });
284
+ });
285
+ ```
286
+
287
+ ### 2. **Manual Security Testing**
288
+ - Penetration testing of OAuth flows
289
+ - Session management testing
290
+ - CSRF attack simulation
291
+ - XSS vulnerability testing
292
+ - SQL injection testing
293
+
294
+ This comprehensive security implementation ensures that your Passport.js OAuth integration follows security best practices and protects both user data and API access.
@@ -0,0 +1,330 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+
3
+ <html xml:lang="en" lang="en">
4
+ <head>
5
+ <title>FHIRsmith: [%title%]</title>
6
+
7
+ <meta charset="utf-8"/>
8
+ <meta content="width=device-width, initial-scale=1.0" name="viewport"/>
9
+ <meta content="http://hl7.org/fhir" name="author"/>
10
+ <meta charset="utf-8" http-equiv="X-UA-Compatible" content="IE=edge" />
11
+
12
+ <link rel="stylesheet" href="/fhir.css"/>
13
+
14
+ <!-- Bootstrap core CSS -->
15
+ <link rel="stylesheet" href="/assets/css/bootstrap.css"/>
16
+ <link rel="stylesheet" href="/assets/css/bootstrap-fhir.css"/>
17
+
18
+ <!-- Project extras -->
19
+ <link rel="stylesheet" href="/assets/css/project.css"/>
20
+ <link rel="stylesheet" href="/assets/css/pygments-manni.css"/>
21
+
22
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
23
+ <!-- [if lt IE 9]>
24
+ <script src="/assets/js/html5shiv.js"></script>
25
+ <script src="/assets/js/respond.min.js"></script>
26
+ <![endif] -->
27
+
28
+ <!-- Favicons -->
29
+ <link sizes="144x144" rel="apple-touch-icon-precomposed" href="/assets/ico/apple-touch-icon-144-precomposed.png"/>
30
+ <link sizes="114x114" rel="apple-touch-icon-precomposed" href="/assets/ico/apple-touch-icon-114-precomposed.png"/>
31
+ <link sizes="72x72" rel="apple-touch-icon-precomposed" href="/assets/ico/apple-touch-icon-72-precomposed.png"/>
32
+ <link rel="apple-touch-icon-precomposed" href="/assets/ico/apple-touch-icon-57-precomposed.png"/>
33
+ <link rel="shortcut icon" href="/assets/ico/favicon.png"/>
34
+ <script type="text/javascript" src="/assets/js/json2.js"></script>
35
+ <script type="text/javascript" src="/assets/js/statuspage.js"></script>
36
+ <script type="text/javascript" src="/assets/js/jquery.min.js"></script>
37
+ <script type="text/javascript" src="/assets/js/jquery-ui.min.js"></script>
38
+ <link rel="stylesheet" href="/assets/css/jquery.ui.all.css">
39
+ <script type="text/javascript" src="/assets/js/jquery.ui.core.js"></script>
40
+ <script type="text/javascript" src="/assets/js/jquery.ui.widget.js"></script>
41
+ <script type="text/javascript" src="/assets/js/jquery.ui.mouse.js"></script>
42
+ <script type="text/javascript" src="/assets/js/jquery.ui.resizable.js"></script>
43
+ <script type="text/javascript" src="/assets/js/jquery.ui.draggable.js"></script>
44
+ <script type="text/javascript" src="/assets/js/jtip.js"></script>
45
+ <script type="text/javascript" src="/assets/js/jcookie.js"></script>
46
+ <script type="text/javascript" src="/assets/js/fhir-gw.js"></script>
47
+
48
+ <style>
49
+ .api-key-display {
50
+ background: #f8f9fa;
51
+ border: 1px solid #dee2e6;
52
+ border-radius: 5px;
53
+ padding: 15px;
54
+ font-family: monospace;
55
+ word-break: break-all;
56
+ margin-bottom: 15px;
57
+ }
58
+
59
+ .usage-stats {
60
+ background: #e9ecef;
61
+ padding: 10px;
62
+ border-radius: 5px;
63
+ margin-bottom: 10px;
64
+ }
65
+
66
+ .oauth-provider {
67
+ display: flex;
68
+ align-items: center;
69
+ padding: 10px;
70
+ border: 1px solid #dee2e6;
71
+ border-radius: 5px;
72
+ margin-bottom: 10px;
73
+ text-decoration: none;
74
+ color: inherit;
75
+ }
76
+
77
+ .oauth-provider:hover {
78
+ background-color: #f8f9fa;
79
+ text-decoration: none;
80
+ color: inherit;
81
+ }
82
+
83
+ .oauth-icon {
84
+ width: 24px;
85
+ height: 24px;
86
+ margin-right: 10px;
87
+ }
88
+
89
+ .status-active { color: #198754; }
90
+ .status-inactive { color: #6c757d; }
91
+
92
+ .table-actions {
93
+ display: flex;
94
+ gap: 5px;
95
+ }
96
+
97
+ .key-warning {
98
+ background: #fff3cd;
99
+ border: 1px solid #ffeaa7;
100
+ border-radius: 5px;
101
+ padding: 15px;
102
+ margin-bottom: 20px;
103
+ }
104
+
105
+ .api-docs {
106
+ background: #f8f9fa;
107
+ border: 1px solid #dee2e6;
108
+ border-radius: 5px;
109
+ padding: 20px;
110
+ margin-top: 20px;
111
+ }
112
+
113
+ .api-endpoint {
114
+ background: #e9ecef;
115
+ padding: 8px 12px;
116
+ border-radius: 3px;
117
+ font-family: monospace;
118
+ font-size: 0.9em;
119
+ margin: 5px 0;
120
+ }
121
+ </style>
122
+
123
+ </head>
124
+
125
+ <body>
126
+
127
+ <div id="segment-navbar" class="segment"> <!-- segment-breadcrumb -->
128
+ <div id="stripe"> </div>
129
+ <div class="container"> <!-- container -->
130
+ <div style="background-color: #ad1f2f; padding: 6px; color: white;"> <!-- container -->
131
+ <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> &copy; HL7.org &nbsp;|&nbsp;
132
+ <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%] &nbsp;|&nbsp;
133
+ <a href="/" style="color: gold"> Server Home</a> &nbsp;|&nbsp;
134
+ <a href="/token" style="color: gold">Token Home</a>
135
+ </div> <!-- /container -->
136
+ </div> <!-- /container -->
137
+ </div>
138
+
139
+ <!-- /segment-breadcrumb -->
140
+
141
+ <div id="segment-content" class="segment"> <!-- segment-content -->
142
+ <div class="container"> <!-- container -->
143
+ <div class="row">
144
+ <div class="inner-wrapper">
145
+ <div class="col-9">
146
+
147
+ <h2><img border="0" src="../FHIRsmith32.png" style="vertical-align: text-bottom"/> Token Server: [%title%] </h2>
148
+
149
+ [%content%]
150
+
151
+ <div class="api-docs">
152
+ <h4>API Documentation</h4>
153
+ <p>Use these endpoints to validate and track API key usage:</p>
154
+
155
+ <div class="mb-3">
156
+ <strong>Validate API Key:</strong>
157
+ <div class="api-endpoint">GET /token/api/validate/{api_key}</div>
158
+ <p>Returns user information and allowed request count.</p>
159
+ </div>
160
+
161
+ <div class="mb-3">
162
+ <strong>Record Usage:</strong>
163
+ <div class="api-endpoint">POST /token/api/usage/{api_key}</div>
164
+ <p>Records API usage. Send <code>{"count": 1}</code> in request body.</p>
165
+ </div>
166
+
167
+ <div class="mb-3">
168
+ <strong>Get Usage Statistics:</strong>
169
+ <div class="api-endpoint">GET /token/api/stats/{api_key}?days=30</div>
170
+ <p>Returns usage statistics for the specified number of days.</p>
171
+ </div>
172
+ </div>
173
+
174
+ </div>
175
+
176
+ </div> <!-- /inner-wrapper -->
177
+ </div> <!-- /row -->
178
+ </div> <!-- /container -->
179
+ </div> <!-- /segment-content -->
180
+
181
+ <div id="segment-footer" class="segment"> <!-- segment-footer -->
182
+ <div class="container"> <!-- container -->
183
+ <div class="inner-wrapper">
184
+ <p>
185
+ <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> &copy; HL7.org 2011+. &nbsp;|&nbsp;
186
+ <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%] &copy; HealthIntersections.com.au 2023+ &nbsp;|&nbsp;
187
+ ([%ms%] ms)
188
+
189
+ </p>
190
+ </div> <!-- /inner-wrapper -->
191
+ </div> <!-- /container -->
192
+ </div> <!-- /segment-footer -->
193
+
194
+ <div id="segment-post-footer" class="segment hidden"> <!-- segment-post-footer -->
195
+ <div class="container"> <!-- container -->
196
+ </div> <!-- /container -->
197
+ </div> <!-- /segment-post-footer -->
198
+
199
+ <!-- JS and analytics only. -->
200
+ <!-- Bootstrap core JavaScript
201
+ ================================================== -->
202
+ <!-- Placed at the end of the document so the pages load faster -->
203
+ <script src="/assets/js/bootstrap.min.js"></script>
204
+ <script src="/assets/js/respond.min.js"></script>
205
+ <script src="/assets/js/fhir.js"></script>
206
+
207
+ <script>
208
+ // Handle API key creation
209
+ document.addEventListener('DOMContentLoaded', function() {
210
+ const createForm = document.getElementById('create-key-form');
211
+ if (createForm) {
212
+ createForm.addEventListener('submit', async function(e) {
213
+ e.preventDefault();
214
+ const formData = new FormData(e.target);
215
+
216
+ try {
217
+ const response = await fetch('/token/keys', {
218
+ method: 'POST',
219
+ headers: { 'Content-Type': 'application/json' },
220
+ body: JSON.stringify({ name: formData.get('name') })
221
+ });
222
+
223
+ const result = await response.json();
224
+
225
+ if (response.ok) {
226
+ // Show the API key in a modal or alert
227
+ showApiKeyModal(result.apiKey);
228
+
229
+ // Reset form and optionally reload
230
+ e.target.reset();
231
+ setTimeout(() => location.reload(), 2000);
232
+ } else {
233
+ alert('Error: ' + result.error);
234
+ }
235
+ } catch (error) {
236
+ alert('Error creating key: ' + error.message);
237
+ }
238
+ });
239
+ }
240
+ });
241
+
242
+ // Show API key in a modal (you might want to use Bootstrap modal instead)
243
+ function showApiKeyModal(apiKey) {
244
+ const modal = document.createElement('div');
245
+ modal.innerHTML = `
246
+ <div style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; display: flex; justify-content: center; align-items: center;">
247
+ <div style="background: white; padding: 30px; border-radius: 10px; max-width: 600px; width: 90%;">
248
+ <h4>API Key Created Successfully</h4>
249
+ <div class="key-warning">
250
+ <strong>Important:</strong> Please save this API key now. You won't be able to see it again!
251
+ </div>
252
+ <div class="api-key-display">
253
+ <strong>Your API Key:</strong><br>
254
+ ${apiKey}
255
+ </div>
256
+ <button onclick="copyToClipboard('${apiKey}')" class="btn btn-secondary me-2">Copy Key</button>
257
+ <button onclick="closeModal()" class="btn btn-primary">Close</button>
258
+ </div>
259
+ </div>
260
+ `;
261
+
262
+ document.body.appendChild(modal);
263
+
264
+ // Auto-close after 30 seconds
265
+ setTimeout(() => {
266
+ if (document.body.contains(modal)) {
267
+ document.body.removeChild(modal);
268
+ }
269
+ }, 30000);
270
+ }
271
+
272
+ function closeModal() {
273
+ const modal = document.querySelector('[style*="position: fixed"]');
274
+ if (modal) {
275
+ document.body.removeChild(modal);
276
+ }
277
+ }
278
+
279
+ function copyToClipboard(text) {
280
+ navigator.clipboard.writeText(text).then(function() {
281
+ alert('API key copied to clipboard!');
282
+ }).catch(function(err) {
283
+ console.error('Could not copy text: ', err);
284
+ // Fallback for older browsers
285
+ const textArea = document.createElement('textarea');
286
+ textArea.value = text;
287
+ document.body.appendChild(textArea);
288
+ textArea.select();
289
+ document.execCommand('copy');
290
+ document.body.removeChild(textArea);
291
+ alert('API key copied to clipboard!');
292
+ });
293
+ }
294
+
295
+ // Handle key deletion
296
+ async function deleteKey(keyId) {
297
+ if (!confirm('Are you sure you want to delete this API key? This action cannot be undone.')) {
298
+ return;
299
+ }
300
+
301
+ try {
302
+ const response = await fetch('/token/keys/' + keyId, {
303
+ method: 'DELETE'
304
+ });
305
+
306
+ if (response.ok) {
307
+ location.reload();
308
+ } else {
309
+ const result = await response.json();
310
+ alert('Error: ' + result.error);
311
+ }
312
+ } catch (error) {
313
+ alert('Error deleting key: ' + error.message);
314
+ }
315
+ }
316
+
317
+ // Form validation
318
+ function validateKeyForm(form) {
319
+ const nameInput = form.querySelector('input[name="name"]');
320
+ if (!nameInput.value.trim()) {
321
+ alert('Please enter a name for your API key');
322
+ nameInput.focus();
323
+ return false;
324
+ }
325
+ return true;
326
+ }
327
+ </script>
328
+
329
+ </body>
330
+ </html>