From 57b84c95588998cf515940da42150f9b2b1f8b10 Mon Sep 17 00:00:00 2001 From: lynnfaraday Date: Sun, 1 Mar 2026 23:14:30 -0500 Subject: [PATCH 1/3] Update default scopes. Upgrade JWT package. --- package-lock.json | 62 ++++++++++++++++++++++++++--------------------- package.json | 2 +- quickstart.js | 4 +-- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8535ef0..b068291 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,14 +9,15 @@ "version": "1.0.0", "dependencies": { "dotenv": "^8.6.0", - "jsonwebtoken": "^9.0.2", + "jsonwebtoken": "^9.0.3", "uuid": "^8.3.2" } }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" }, "node_modules/dotenv": { "version": "8.6.0", @@ -30,16 +31,18 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" } }, "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", "dependencies": { - "jws": "^3.2.2", + "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -56,21 +59,23 @@ } }, "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, @@ -131,7 +136,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/semver": { "version": "7.6.3", @@ -157,7 +163,7 @@ "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, "dotenv": { "version": "8.6.0", @@ -173,11 +179,11 @@ } }, "jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", "requires": { - "jws": "^3.2.2", + "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -190,21 +196,21 @@ } }, "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "requires": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "requires": { - "jwa": "^1.4.1", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, diff --git a/package.json b/package.json index be24c7a..15aa740 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "dependencies": { "dotenv": "^8.6.0", - "jsonwebtoken": "^9.0.2", + "jsonwebtoken": "^9.0.3", "uuid": "^8.3.2" } } diff --git a/quickstart.js b/quickstart.js index 4e0718a..4ccfd58 100644 --- a/quickstart.js +++ b/quickstart.js @@ -116,7 +116,7 @@ async function quickstart() { response = await getFromApi(serviceAccessToken, url); const participants = response.data; console.log(`\nTotal Participants: ${participants.totalParticipants}`); - + console.log(participants["participants"][0]); // Get a specific participant by identifier. We disable 'raiseError' here // so we can handle the 404 case ourselves. const participantIdentifier = "YOUR_PARTICIPANT_IDENTIFIER" @@ -135,7 +135,7 @@ async function quickstart() { // Be sure to: // 1. Use the internal ID field (from participant.id above) and NOT participantIdentifier // 2. Request the correct scope(s) for your needs. - const scopes = "Participant:read SurveyAnswers:read" + const scopes = "Participant:read SurveyAnswers:read user/*.read api" const participantAccessToken = await getParticipantAccessToken(serviceAccessToken, participant.id, scopes); console.log(`\nObtained participant access token for ${participant.id}: ${participantAccessToken}`); } From 3e5063d8cca138132d7a15efc7d405e19a7abcd3 Mon Sep 17 00:00:00 2001 From: lynnfaraday Date: Fri, 20 Mar 2026 13:26:43 -0400 Subject: [PATCH 2/3] Add post to API. --- quickstart.js | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/quickstart.js b/quickstart.js index 4ccfd58..e64f4f1 100644 --- a/quickstart.js +++ b/quickstart.js @@ -7,18 +7,18 @@ const { v4: uuidv4 } = require('uuid'); // **NOTE!** In a real production app you would want these to be sourced from real environment variables. The .env file is just // a convenience for development. -const rksProjectId = process.env.RKS_PROJECT_ID; -const rksServiceAccount = process.env.RKS_SERVICE_ACCOUNT; +const mdhProjectId = process.env.RKS_PROJECT_ID; +const mdhServiceAccount = process.env.RKS_SERVICE_ACCOUNT; const privateKey = process.env.RKS_PRIVATE_KEY; -const baseUrl = 'https://designer.mydatahelps.org'; +const baseUrl = "https://mydatahelps.org" const tokenUrl = `${baseUrl}/identityserver/connect/token`; async function getServiceAccessToken() { const assertion = { - "iss": rksServiceAccount, - "sub": rksServiceAccount, + "iss": mdhServiceAccount, + "sub": mdhServiceAccount, "aud": tokenUrl, "exp": Math.floor(new Date().getTime() / 1000) + 200, "jti": uuidv4() @@ -73,6 +73,29 @@ async function getFromApi(serviceAccessToken, resourceUrl, queryParams = null, r return { data: await response.json(), status: response.status }; } +async function postToApi(serviceAccessToken, resourceUrl, queryParams = null, body = null, raiseError = true) { + + const queryString = queryParams ? `?${new URLSearchParams(queryParams)}` : ''; + const url = `${baseUrl}${resourceUrl}${queryString}`; + + const response = await fetch(url, { + headers: { + "Authorization": `Bearer ${serviceAccessToken}`, + "Accept": "application/json", + "Content-Type": "application/json; charset=utf-8" + }, + method: 'POST', + body: JSON.stringify(body) + }); + + if (!response.ok && raiseError) { + const error = `API call to ${url} failed. ${response.status} - ${await response.text()}`; + throw new Error(error); + } + + return { data: await response.json(), status: response.status }; +} + // Get a participant access token for the specified participant // Used for MyDataHelps Embeddables ONLY async function getParticipantAccessToken(serviceAccessToken, participantID, scopes) { @@ -112,7 +135,7 @@ async function quickstart() { console.log(serviceAccessToken); // Get all participants - url = `/api/v1/administration/projects/${rksProjectId}/participants`; + url = `/api/v1/administration/projects/${mdhProjectId}/participants`; response = await getFromApi(serviceAccessToken, url); const participants = response.data; console.log(`\nTotal Participants: ${participants.totalParticipants}`); @@ -122,7 +145,7 @@ async function quickstart() { const participantIdentifier = "YOUR_PARTICIPANT_IDENTIFIER" if (participantIdentifier != "YOUR_PARTICIPANT_IDENTIFIER") { - url = `/api/v1/administration/projects/${rksProjectId}/participants/${participantIdentifier}`; + url = `/api/v1/administration/projects/${mdhProjectId}/participants/${participantIdentifier}`; response = await getFromApi(serviceAccessToken, url, null, false ); if (response.status === 404) { console.log("\nParticipant not found."); @@ -135,7 +158,7 @@ async function quickstart() { // Be sure to: // 1. Use the internal ID field (from participant.id above) and NOT participantIdentifier // 2. Request the correct scope(s) for your needs. - const scopes = "Participant:read SurveyAnswers:read user/*.read api" + const scopes = "Participant:read SurveyAnswers:read api" const participantAccessToken = await getParticipantAccessToken(serviceAccessToken, participant.id, scopes); console.log(`\nObtained participant access token for ${participant.id}: ${participantAccessToken}`); } From 9569de3b80ec90e508079095a1ba555d27bf83b5 Mon Sep 17 00:00:00 2001 From: lynnfaraday Date: Fri, 20 Mar 2026 22:27:49 -0400 Subject: [PATCH 3/3] Typo --- quickstart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickstart.js b/quickstart.js index e64f4f1..ad8a3e0 100644 --- a/quickstart.js +++ b/quickstart.js @@ -11,7 +11,7 @@ const mdhProjectId = process.env.RKS_PROJECT_ID; const mdhServiceAccount = process.env.RKS_SERVICE_ACCOUNT; const privateKey = process.env.RKS_PRIVATE_KEY; -const baseUrl = "https://mydatahelps.org" +const baseUrl = "https://mydatahelps.org"; const tokenUrl = `${baseUrl}/identityserver/connect/token`; async function getServiceAccessToken() {