In my Javascript code (executed in a Firebase Cloud Function) I need to set some device environment variables via the Notehub API.
In the Notehub API documentation here we’re told to use ClientID and ClientSecret credentials and that username/password authorization is deprecated. But in the notehub-js library documentation here the only example uses username/password.
I’ve written code using the ClientID/ClientSecret and I successfully get a token. But when my code reaches this statement:
await deviceApi.putDeviceEnvironmentVariables(projectUID, notecardDevID, envVars);
my logs indicate a 403 error as follows:
err: 'The requested action was forbidden',
code: 403,
status: 'Forbidden',
request: '/v1/projects/app%3A6a95d054-fb1b-4dac-97ae-4fc9c12156a6/devices/dev%3A860322068122933/environment_variables',
details: {
body: '{"SmartTriggerCountMin":"0","SmartTriggerDistMax":"240","SmartTriggerDistMin":"0","SmartTriggerThresh":"0","BrightnessStep":"5","TempAdjust":"-5"}'
},
debug: 'denied access to identifier account:anonymous, resource blues:resources:app:5258:devices, actions update'
}
I’m stumped. Can you help me figure out what is wrong? Thank you in advance! Mark
In case it’s helpful, I’m going to show the whole code block, including getting the authorization and attempting to set the environment variables:
// Authenticate using OAuth 2.0 client credentials
async function getNotehubToken() {
console.log("Requesting Notehub OAuth token...");
const response = await fetch("https://notehub.io/oauth2/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: notehubClientID,
client_secret: notehubClientSecret,
}),
});
if (!response.ok) {
const errorText = await response.text();
console.error("Failed to get token:", errorText);
return "";
}
const data = await response.json();
console.log("Notehub OAuth token received.");
return data.access_token;
}
// Inject token into Notehub JS client
function configureNotehubClient(token) {
const defaultClient = NotehubJs.ApiClient.instance;
const apiKeyAuth = defaultClient.authentications["api_key"];
apiKeyAuth.apiKey = token;
}
// Main update routine
async function uploadDeviceParameters() {
console.log("Checking for devices that need SmartTrigger update...");
try {
const devicesSnapshot = await db.collection("devices")
.where("NeedsUpdate", "==", true)
.limit(5)
.get();
if (devicesSnapshot.empty) {
console.log("No devices need update this hour.");
return;
}
const token = await getNotehubToken();
if (!token) {
console.error("Aborting — could not get OAuth token.");
return;
}
configureNotehubClient(token);
const deviceApi = new NotehubJs.DeviceApi();
for (const deviceDoc of devicesSnapshot.docs) {
const deviceData = deviceDoc.data();
const deviceID = deviceData.DeviceID;
const notecardDevID = deviceData.NotecardDevID;
if (!notecardDevID) {
console.log(`Skipping ${deviceID} — missing NotecardDevID.`);
continue;
}
const envVars = {
SmartTriggerCountMin: String(deviceData.SmartTriggerCountMin ?? 0),
SmartTriggerDistMax: String(deviceData.SmartTriggerDistMax ?? 600),
SmartTriggerDistMin: String(deviceData.SmartTriggerDistMin ?? 0),
SmartTriggerThresh: String(deviceData.SmartTriggerThresh ?? 0),
BrightnessStep: String(deviceData.BrightnessStep ?? 7),
TempAdjust: String(deviceData.TempAdjust ?? 0)
};
console.log(`Updating environment for ${deviceID}:`, envVars);
try {
await deviceApi.putDeviceEnvironmentVariables(projectUID, notecardDevID, envVars);
console.log(`✅ Updated env for ${deviceID}`);
await db.collection("devices").doc(deviceDoc.id).update({
NeedsUpdate: false
});
} catch (updateError) {
console.error(`❌ Failed for ${deviceID}:`, updateError.body || updateError.message);
}
}
console.log(`Environment update complete for ${devicesSnapshot.size} device(s).`);
} catch (error) {
console.error("Unexpected error in uploadDeviceParameters:", error);
}
}
1 Like
Hey @mark_pdx,
Sorry you ran into issues here. Unfortunately at the moment our Notehub JS library only officially supports our older authentication mechanism Notehub API Introduction - Blues Developers.
You’re hitting issues because the defaultClient.authentications["api_key"]
API is expecting a session token and you’re providing an oauth token.
You can get Notehub JS to work with oauth tokens, but it’s a bit of extra work. Building on your getNotehubToken()
function I was able to get this snippet of code to return data on one of my devices.
const main = async () => {
const token = await getNotehubToken();
NotehubJs.ApiClient.instance.authentications.oauth2 = {
type: "oauth2",
accessToken: token,
};
const projectUID = "app:a84da1c1-39ab-4e40-85ec-2e502f7e076a";
const deviceUID = "dev:0080e115004530ab";
const response = await NotehubJs.ApiClient.instance
.callApi(
`/v1/projects/${projectUID}/devices/${deviceUID}`,
"GET", // method
{}, // path params
{}, // query params
{}, // headers params
{}, // form params
undefined, // body
["oauth2"], // auth names
["application/json"], // content types
["application/json"] // accepts
)
.then((data) => {
console.log("Device data: ", JSON.stringify(data.response.text));
});
};
main();
(For a more through example, see this post from one of our community members on GitHub.)
It’s a bit clunky, but it does work. Alternatively, you could hit the Notehub HTTP APIs directly, much like you’re doing to get the auth token itself.
Let me know if you have any questions or if other examples would help. Sorry this isn’t as straightforward as should be.
TJ
2 Likes
@tjvantoll Thank you for the prompt reply! It helped a lot, although I still have a few questions.
I abandoned oAuth, and instead used the cURL command to manually grab a session token, using my username/password. When I embedded that token in my code the Notehub API responded as hoped. Progress!
Key question: is that session token going to work indefinitely? Or do I need to grab a fresh one for my periodic updates (a few hours or days apart)? If I need to grab a fresh one, I guess it means embedding my username/password in my code, which seems unwise. Looking forward to your suggestions.
Hey @mark_pdx,
Nice! Glad to hear you got it working.
And session tokens work indefinitely. But because of that, you’ll want to treat those tokens with the same amount of caution you would use for username / password. I’m not familiar with Firebase Cloud Functions specifically, but I’m guessing they some mechanism for storing secrets.
TJ
1 Like
Thanks, TJ, your advice has resolved my question!
1 Like