Jelastic API intermittent session authentication error "not authenticated (different session key)" - node.js

I have created the below script to pull stats from the Jelastic api so i can gather resource stats over a time. The end goal is to log the data to a spreadsheet.
Below is my code that handles authenticating and then making the request to GetSumStats.
If i run the code, some of the time the results returned are expected.
{
iops_used: 0,
duration: 3600,
cpumhz: 7,
start: '',
disk: 7857,
mem: 725212,
cpu: 24002,
capacity: 9,
net: { in_int: 96004, out_int: 96004, in_ext: 9181, out_ext: 9395 },
chanksused: 7,
nodeid: 'sum'
}
But other times the request fails with the error.
{ result: 702,
source: 'JEL',
error: 'not authenticated (different session key)',
stats: [] }
Is this a timing issue or a known issue? Maybe the script is too fast and the API doesn't know about the session id yet? That's why i introduced the setTimeout
var sites = require('./sites.json').sites,
credentials = require('./credentials.json'),
Client = require('node-rest-client').Client,
util = require('./util.js');
(function () {
"use strict";
var client = new Client();
var session;
login();
function login() {
var args = {
parameters: {
appid: sites[2].appId,
login: credentials.email,
password: credentials.password
}
};
client.registerMethod("login", "https://app.j.hostapi.co.uk/1.0/users/authentication/rest/signin", "GET");
client.methods.login(args, function (data, response) {
// parsed response body as js object
data = util.parseResponse(data);
session = data.session;
console.log(session);
// Tried to pause here in case it was too quick
setTimeout(function() {
getSumStats();
}, 3000);
});
}
function logout() {
var args = {
parameters: {
appid: sites[2].appId,
session: session
}
};
client.registerMethod("logout", "https://app.j.hostapi.co.uk/1.0/users/authentication/rest/signout", "GET");
client.methods.logout(args, function (data, response) {
// parsed response body as js object
data = util.parseResponse(data);
console.log(data);
// raw response
//console.log(response);
});
}
// Failure here
function getSumStats() {
var args = {
parameters: {
domain: sites[2].domain,
session: session,
duration: 3600
}
};
client.registerMethod("getSumStats", "https://app.j.hostapi.co.uk/1.0/environment/control/rest/getsumstat", "GET");
client.methods.getSumStats(args, function (data, response) {
// parsed response body as js object
data = util.parseResponse(data);
console.log(data.stats);
logout();
});
}
})();

You are limited to 1 concurrent login session. The login is pinned by IP / User Agent.
If you need to create multiple concurrent login sessions, you can try using a unique UA per session to avoid conflicts.

Related

http request with status 200 but no response data

I am trying to make an http request on refresh within my Angular frontend to a nodejs backend and expect to receive a token as response. Sometimes the request gets cancelled and even if its successful (200) i do not get send the token in the response.
When i make a postman request the request is always successful and sends the token, also when i make the request in Angular constructor without the refresh logic, so i suspect it has something to do with the usage of rxjs but can not figure out whats the problem.
here is the logic of refresh in app.component
constructor(
private router: Router,
private auth: AuthService
) {
// this.auth.requestServer(ServerMethod.GET, '/refresh').subscribe() // this request would work fine
router.events.pipe(
switchMap((event) => {
if (event instanceof NavigationStart) {
const browserRefresh = !router.navigated;
if (browserRefresh) {
this.auth.deleteAccessToken();
return this.auth.requestServer(ServerMethod.GET, '/refresh');
}
}
return EMPTY;
})
).subscribe();
}
here is deleteAccessToken()
deleteAccessToken() {
sessionStorage.removeItem('accessToken');
this.tokenSubject.next(null);
}
requestServer()
requestServer(type: ServerMethod, path?: string, data?: any): Observable<any> {
let httpOptions: httpOptions;
switch (type) {
case ServerMethod.POST:
return this.server.post(path, data).pipe(tap(res => this.handleAccessToken(res)));
case ServerMethod.GETALL:
return this.server.getAll(this.getAllPath);
case ServerMethod.GET:
return this.server.get(path).pipe(tap(res => this.handleAccessToken(res)));
default:
return EMPTY;
}
}
here is server get method
get(path: string): Observable<any> {
const url = this.serverAdress + path;
return this.http.get(url);
}
and in my nodejs backend here is the refresh logic:
module.exports.refresh_get = async (req, res) => {
if (req.cookies && req.cookies.refreshToken) {
// Destructuring refreshToken from cookie
const refreshToken = req.cookies.refreshToken;
// Verifying refresh token
jwt.verify(refreshToken, 'secret',
(err, decodedToken) => {
if (err) {
// Wrong Refesh Token
res.status(406).json({ message: 'wrong refresh token' });
}
else {
// create new accessToken
const accessToken = createToken(decodedToken.id);
// create new refreshToken and set it in cookie and delete old cookie
const newRefreshToken = jwt.sign({
id: decodedToken.id,
}, 'secret', { expiresIn: '1d' });
res.cookie('refreshToken', newRefreshToken, { httpOnly: true,
sameSite: 'None', secure: true,
maxAge: 24 * 60 * 60 * 1000 });
res.status(200).json({ accessToken });
}
})
} else {
res.status(406).json({ message: 'Unauthorized' });
}
}
request in network tab on refresh looks then like this:
but Response is empty, there should be an object { accessToken: '...' }
ChatGPT answered my question:
It's possible that the problem lies with the switchMap operator in the router.events observable. The switchMap operator cancels the previous inner observable when a new value is emitted, which could result in the HTTP request being cancelled if it takes too long to complete.
To ensure that the HTTP request is not cancelled, you can try using the concatMap operator instead of switchMap. concatMap will wait for the previous observable to complete before starting a new one, which will prevent the HTTP request from being cancelled prematurely.
Thanks ChatGPT.

MSAL js, AAD B2C Multi Factor Authentication, 400 Bad Request, Request header too long

MSAL js Version: v0.2.4;
Chrome Version: 79.0.3945.88 (Official Build) (64-bit)
From the various post It is understood that due to cookies piled up, we are seeing '400 Bad Request - Request header too long', But it is not happening in all my developer environments.
I would like to know, why it is not with local environments (running from VS Code) but in deployed environments(Azure App Service)
I can update the MSAL package to latest version, but at the same time previously it was working fine in deployed environments but not now, why?
Is there any connection with scope error message (AADB2C90055) with 'Bad Request - Request header too long' ?
AADB2C90055: The scope 'openid profile' must specify resource
Any sort of information will be useful to me or other folks, and thanks in advance
Here is the Code being used in My App,
let userAgentApplication: Msal.UserAgentApplication;
const createAuthorityUrl = (tenantId: string, policy: string) => {
return `https://${tenantId}.b2clogin.com/tfp/${tenantId}.onmicrosoft.com/${policy}`;
};
export const b2cLogin = (config: B2CConfig) => {
const msalAppConfig = {
cacheLocation: 'localStorage',
redirectUri: `${location.protocol}//${location.host}`,
navigateToLoginRequestUrl: false,
storeAuthStateInCookie: true,
validateAuthority: false,
};
const { clientId, tenantId, myb2cSigninPolicy, myb2cPasswordResetPolicy } = config;
return new Promise(resolve => {
let handlingPasswordReset = false;
const app = new Msal.UserAgentApplication(
clientId,
createAuthorityUrl(tenantId, myb2cSigninPolicy),
(errorDesc: string, token: string) => {
if (errorDesc && errorDesc.indexOf('AADB2C90118') > -1) {
// user forgot password
// https://github.com/Azure-Samples/active-directory-b2c-javascript-msal-singlepageapp/issues/9#issuecomment-347556074
handlingPasswordReset = true;
new Msal.UserAgentApplication(
clientId,
createAuthorityUrl(tenantId, myb2cPasswordResetPolicy),
() => null,
msalAppConfig,
).loginRedirect();
}
return resolve(token);
},
msalAppConfig,
);
if (!handlingPasswordReset) {
userAgentApplication = app;
}
// Seems that MSAL's acquireTokenSilent() won't resolve if run within an iframe
if (window.parent !== window) {
return resolve('');
}
if (!userAgentApplication.isCallback(location.hash)) resolve(getAccessToken());
});
};
export const getAccessToken = async (): Promise<string> => {
if (!userAgentApplication) {
throw new Error('getAccessToken attempted before authentication initialized');
}
try {
return await userAgentApplication.acquireTokenSilent(['openid']);
} catch (error) {
console.log(error);
return '';
}
};
The error HTTP 400: Size of header request is too long generally happens because there's too many cookies or cookies that are too big.
reference:
Azure Portal: Bad Request - Request Too Long

Authentication Error when Retrieving and Editing Device Configuration on IoT-Core

I'm trying to use a backend nodeJS server to access (and edit) the device configuration on IoT-Core referring to this API docs
However, I keep getting error:
code 401 with error message "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED".
I created a service account and a key from Google IAM, and gave it Cloud IoT Device Controller permissions, which could update device configurations but not create or delete. Subsequently, I changed it to Cloud IoT Admin and even Project Editor permissions, but still saw the same error message. Am I getting the keys all wrong, or not doing something else I should be doing?
Code below was how I invoked the request
function createJwt (projectId, privateKeyFile, algorithm) {
// Create a JWT to authenticate this device. The device will be disconnected
// after the token expires, and will have to reconnect with a new token. The
// audience field should always be set to the GCP project ID.
const token = {
'iat': parseInt(Date.now() / 1000),
'exp': parseInt(Date.now() / 1000) + 20 * 60, // 20 minutes
'aud': projectId
};
const privateKey = fs.readFileSync(privateKeyFile);
return jwt.sign(token, privateKey, { algorithm: algorithm });
}
app.get('/', function(req, res){
let authToken = createJwt('test-project', './keys/device-config.pem', 'RS256');
const options = {
url: 'https://cloudiot.googleapis.com/v1/projects/test-project/locations/us-central1/registries/dev-registry/devices/test-device',
headers: {
'authorization': 'Bearer ' + authToken,
'content-type': 'application/json',
'cache-control': 'no-cache'
},
json: true
}
request.get(options, function(error, response){
if(error) res.json(error);
else res.json(response);
})
});
For backend servers to interact with IoT-Core, the authentication method is not the same as for device MQTT or HTTP connections. Reference: https://cloud.google.com/iot/docs/samples/device-manager-samples#get_a_device
I was able to retrieve and update device configurations using the code below
function getClient (serviceAccountJson, cb) {
const serviceAccount = JSON.parse(fs.readFileSync(serviceAccountJson));
const jwtAccess = new google.auth.JWT();
jwtAccess.fromJSON(serviceAccount);
// Note that if you require additional scopes, they should be specified as a
// string, separated by spaces.
jwtAccess.scopes = 'https://www.googleapis.com/auth/cloud-platform';
// Set the default authentication to the above JWT access.
google.options({ auth: jwtAccess });
const DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest';
const API_VERSION = 'v1';
const discoveryUrl = `${DISCOVERY_API}?version=${API_VERSION}`;
google.discoverAPI(discoveryUrl, {}, (err, client) => {
if (err) {
console.log('Error during API discovery', err);
return undefined;
}
cb(client);
});
}
function getDevice (client, deviceId, registryId, projectId, cloudRegion) {
const parentName = `projects/${process.env.GCP_PROJECT_ID}/locations/${cloudRegion}`;
const registryName = `${parentName}/registries/${registryId}`;
const request = {
name: `${registryName}/devices/${deviceId}`
};
const promise = new Promise(function(resolve, reject){
client.projects.locations.registries.devices.get(request, (err, data) => {
if (err) {
console.log('Could not find device:', deviceId);
console.log(err);
reject(err);
} else {
console.log(data.config.binaryData);
resolve(data);
}
});
});
return promise;
}
app.get('/', function(req, res){
const cb = function(client){
getDevice(client, 'test-device', 'dev-registry', process.env.GCP_PROJECT_ID, 'us-central1')
.then(function(response){
let decoded = new Buffer(response.config.binaryData, 'base64').toString();
res.json(decoded);
})
.catch(function(error){
res.json(error);
})
}
getClient(serviceAccountJson, cb);
});
I think what you're looking to do is best accomplished using the client library for NodeJS.
First, retrieve an API client object as done in the sample. This will take in the service account credentials you used and will authenticate against Google API Core servers.
At the point in the referenced code where cb(client); is invoked, you'll have your client object and are ready to update your device. Add the imports and API constants from the sample and replace the code where you have a client object with the following code and you should be set.
Use some strings for your device identifiers:
const projectId = 'my-project';
const cloudRegion = 'us-central1';
const registryId = 'my-registry';
const deviceId = 'my-device;
const config = '{fan: 800}';
Next, form your device String:
const deviceId = `projects/${projectId}/locations/${cloudRegion}/registries/${registryId}/devices/${deviceId}`;
const binaryData = Buffer.from(config).toString('base64');
Now you form your request object and execute:
const request = {
name: `${registryName}`,
versionToUpdate: 0,
binaryData: binaryData
};
console.log(request);
client.projects.locations.registries.devices
.modifyCloudToDeviceConfig(
request,
(err, data) => {
if (err) {
console.log('Could not update config:', deviceId);
console.log('Message: ', err);
} else {
console.log('Success :', data);
}
});
Your configuration is updated. If your device is subscribed to the config topic on MQTT it will receive the latest configuration, otherwise, you can poll for the configuration with HTTP from your device.
Just to confirm, when you created the SSL key pair, and when you registered the device with the Cloud IoT Core registry, did you match the type of key created with the radio button you registered it with?
Also to confirm, you put the Google root certificate on the device in the same directory as the private key: ./keys/device-config.pem ? If not you can fetch it with: wget https://pki.google.com/roots.pem.

Lambda function timing out after 10 seconds

Code:
const knex = require('knex')({
client: 'mysql',
connection: {
host: process.env.database_host,
user: process.env.database_user,
password: process.env.database_pass,
database: process.env.database_db,
charset: 'utf8'
}
});
const bcrypt = require('bcrypt');
const bookshelf = require('bookshelf')(knex);
const User = bookshelf.Model.extend({
tableName: 'users'
});
const checkValues = (values) => {
// todo: add data validation
return true;
};
exports.test = (database) => {
// todo: add tests
};
exports.handler = (event, context, callback) => {
let salt = bcrypt.genSaltSync();
let values = {
first_name: event.firstname,
last_name: event.lastname,
username: event.username,
date_of_birth: event.birthday,
password: bcrypt.hashSync(event.password, salt),
password_salt: salt
};
if (!checkValues(values)) {
callback(null, {
success: false,
error: {
id: 2,
details: 'data validation error'
}
});
context.done(null, "User not created");
return;
}
try {
new User({
'first_name': values.first_name,
'last_name': values.last_name,
'username': values.username,
'date_of_birth': values.date_of_birth,
'password': values.password,
'password_salt': values.password_salt
}).save();
callback(null, {
success: true
});
context.done(null, "User created");
} catch (err) {
console.log(err);
callback(null, {
success: false,
error: {
id: 1,
details: 'error inserting user into database'
}
});
context.done(null, "User not created");
}
};
I am trying to make a basic sign up api endpoint using AWS API Gateway and Lambda functions, however every time I post the information to the api gateway I get the error
{
"errorMessage": "2017-09-07T08:38:50.174Z f2368466-93a7-11e7-b4bc-01142a109ede Task timed out after 10.00 seconds"
}
I have tried using different database libraries but I seem to always be hitting the same problem. The database connection works I know this because the user does infact get added to the users table in the database and the password is successfully hashed..
I have also tried using asynchronous bcrypt but it doesn't make any difference to the result, it still does it but says it times out.
Lambda doesn't seem to be terminating properly, something keeps the process still running and I can't figure out what, any ideas?
i had the similar issue using API gateway invoking my lambda.
The default timeout for API gateway is 30 seconds. If your response is not ready within in 30 seconds, you will be timed out though your lambda would still run!
So may be try to get the response back within 30 seconds. If not have one lambda being invoked from the API and give the response back immediately and let the first lambda invoke your second lambda and that will run upto max time which is 5 mins.
Thanks

nodejs restclient authentication and fetch session data

I am new nodejs, Currently I am using rest-client to fetch the data from proxy service. Initially By using POST method of rest Client I am able to login my proxy and I am getting a success response.
after login Immediately I am calling 'get'(proxy/user/me) method for fetching the session data, then I am facing login failed message, How can I will check each time before fetching any other data using nodejs.
//Example POST method invocation for Login
//after Login get method invocation
var Client = require('node-rest-client').Client;
var client = new Client();
var fs = require('fs');
var email = "raj#ioynx.io";
var proxy = "http://google.com";
var password = "abcd";
// set content-type header and data as json in args parameter
var args = {
data: JSON.stringify({ username: email, password: password }),
headers: { "Content-Type": "application/json" }
};
client.post(proxy+"login", args, function (data, response) {
//Sucessfully Login message Dis[play with status Code 200
console.log(response.statusCode);
if (parseInt(response.statusCode) === 200) {
//after success I am trying to fetch session Data, 'get' mehode will always shows ,there is no login.
client.get(proxy+"user/me", function (sessionData, sessionResponse) {
//The second URL for fetching session data always shows { message: "not Login message" }
console.log(sessionData, 'Dubt', sessionResponse.statusCode);
});
}
});
You will need to wrap each method with some sort of authentication checker. Something like this-
function wrapAuthentication(fn) {
return function() {
// Check authentication
if(authenticated) {
fn.apply(fn, arguments);
}
}
}
function updateUserData() {
// Do stuff
}
client.post(proxy+"login", args, wrapAuthentication(updateUserData));
You're rest framework may support something like this already.

Resources