How to renew a session of salesforce using jsforce? - node.js

I am using jsforce node module for doing CRUD operation in salesforce.
For making a connection to salesforce, I have following input
username, password, securityToken and loginUrl.
Here's how I make a connection first time.
var conn = new jsforce.Connection({
loginUrl: connectionDetails.salesforce.loginUrl
});
conn.login(connectionDetails.salesforce.username,
connectionDetails.salesforce.password + connectionDetails.salesforce.securityToken,
function(err, userInfo) {
if (!err) {
console.log('User with user id ' + userInfo.id + ' successfully logged into Salesforce');
successCb(conn.accessToken, conn.instanceUrl);
} else {
console.log('Login failed to https://test.salesforce.com/');
errorCb('Login failed to https://test.salesforce.com/');
}
});
I store the accessToken and Instanceurl in the req object provided by Express.
After that any CRUD operation I perform like below
var salesConn = new jsforce.Connection({
accessToken: salesforceAccessToken,
instanceUrl: salesforceInstanceUrl
});
salesConn.sobject('Lead').retrieve(someLeadID, function(err, data) {
...
});
Now suppose I keep my server idle for few hours or may be even a day, then if I do a CRUD operation then the call fails. This I am pretty sure that the session has expired.
Now I have two queries
Is the above correct way of making connection to salesforce using the input connection details I have?
How can I know that the session has expired and make a new session?
PS
I tried to look into the Access Token with Refresh Token, but that is only available with OAuth2 authorization code flow.

A little late, but I had the same issue today and I fixed it by calling conn.login(username, password+token) whenever I get an invalid session error.
I am doing something different though, I'm not creating a second variable to use with my SF calls, but instead use the original conn variable, conn.sobject(...).

It would refresh token automatically.
my jsforce version is "jsforce": "^1.4.1"
jsforce has a _refreshDelegate
Connection.prototype.login = function(username, password, callback) {
// register refreshDelegate for session expiration
this._refreshDelegate = new HttpApi.SessionRefreshDelegate(this, createUsernamePasswordRefreshFn(username, password));
if (this.oauth2 && this.oauth2.clientId && this.oauth2.clientSecret) {
return this.loginByOAuth2(username, password, callback);
} else {
return this.loginBySoap(username, password, callback);
}
};

Related

nodejs with ldapjs authentication possible without password

I gonna write a API which get a Username and password from the Front-End. This username and password get passed into my LDAP bind and should get checked if these informations are correct. Now I got the problem when the use types a wrong password I get the correct error code 49 for invalid credentials. But when the user just enter his username and nothing into password, then LDAP is automatically accepting it and passes through the authentication.
Can maybe someone help me or give me an advice whats wrong?
const ldap = require('ldapjs');
var client = ldap.createClient({
url: `ldap://${process.env.LDAP_HOST}:${process.env.LDAP_PORT}`
});
function ldapauth(dn, password, callback) {
var serverStatus;
//dn = entry.object.dn from another ldap query
client.bind(dn, password, function(err, res) {
if(err) {
console.log(['Error:',err.code, err.dn, err.message]);
serverStatus = err.code;
client.unbind();
return callback (serverStatus);
} else {
console.log('Auth Status: ' + res.status);
if(res.status == 0) {
serverStatus = res.status;
} else {
serverStatus = 500;
}
client.unbind();
return callback(serverStatus);
};
});
}
This is my output which I get when the password is empty
The activedirectory package solves this by returning an error if no password is supplied:
if ((! username) || (! password)) {
var err = {
'code': 0x31,
'errno': 'LDAP_INVALID_CREDENTIALS',
'description': 'The supplied credential is invalid'
};
return(callback(err, false));
}
Here is the code: https://github.com/gheeres/node-activedirectory/blob/master/lib/activedirectory.js#L1803
More info about the bind command: https://ldap.com/the-ldap-bind-operation/
This sounds like is the cause of your issue:
An anonymous simple bind can be performed by providing empty strings as the bind DN and password (technically, the LDAPv3 specification states that only the password must be empty, but this has been responsible for many security problems with LDAP clients in the past, and many servers require that if an empty password is provided then an empty DN must also be given).

Should I need to refresh the page in order to get user data?

Right now I have to refresh the page in order to get user data from local storage, I'm not sure that this is the correct way of doing things. How would I go about getting user data on login? Right now I'm just grabbing it from a local storage JWT but that doesn't seem like best practice. Should I be getting the user based on their ID every time they log in? How do I persist that data?
reload(){
this.router.navigate(['/']);
}
login(userName: string, email: string, password: string) {
const authData: LoginData = {userName, email, password };
console.log('AuthData', authData);
this.http.post<{token: string, expiresIn: number, displayName: string}>(this.loginRoute, authData)
.subscribe(response => {
const token = response.token;
this.token = token;
if (token) {
const expiresInDuration = response.expiresIn;
this.setAuthTimer(expiresInDuration);
this.isAuthenticated = true;
this.username = response.displayName;
this.authStatus.next(true);
const now = new Date();
const expirationDate = new Date(now.getTime() + expiresInDuration * 1000);
console.log(expirationDate);
this.saveAuthData(token, expirationDate, userName);
this.reload();
}
});
}
I'd like to be able to display the user data on my home page of the user that is currently logged in but am unsure how to persist that data. right now I'm just using local storage.
Without a backend, you don't have a ton of options (like websockets). But with what you have you could create a separate function from login that checks if the user is actually authenticated and put it in an interval and poll it every X seconds. This will get rid of the reload function. You would create an Observable onInit that just goes off every 10 seconds or whatever and checks the status of the token.
Cheers!

Find a better way to renew AWS credentials

I am using sts:assumeRole to connect to a s3 bucket of a different account.
Now, the job that I run takes a few days and along the way the credentials expire and I needed a way to renew them.
I have written the following code to handle expiry of the temporary credentials
This code is inside my downloadFile():
return new Promise((resolve, reject) => {
function responseCallback(error, data) {
if (error) {
const errorMessage = `Fail to download file from s3://${config().s3.bucket}/${path}: ${error};`;
reject(error);
} else {
Logger.info(`Successfully download file from s3://${config().s3.bucket}/${path}`);
resolve(data.Body);
}
}
const fn = this.s3Client.getObject({
Bucket: config().s3.bucket,
Key: path
}, (error, data) => this.handleTokenExpiry(error, data, fn, responseCallback));
});
And this is the handleTokenExpiry()
handleTokenExpiry(error, data, fn, callback) {
if (!error || error.code !== "ExpiredToken") return callback(error, data);
Logger.info("Token expired, creating new token");
this.s3Client = null; // null so that init() doesn't return existing s3Client
return this.init().then(fn);
}
Here init() is the method which sets this.s3Client using sts:assumeRole
and then new AWS.S3()
This works fine but I am not sure if this a clean way to do it. The strange thing is when I test it in local it takes almost two minutes for responseCallback() to be called when token is expired. Though responseCallback() gets executed immediately while the token is active.
For tasks running less than 12h, here is the solution.
When using AssumeRole, you can specify DurationSeconds argument to specify the duration of the temporary credentials returned by STS. This is 15 min minimum, up to 12h.
The role you are assuming needs to be modified to authorize the maximum duration too. See https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html#id_roles_use_view-role-max-session
In your case, with a job running for several days. I would suggest to refactor the app to run in smaller batches, each running for a few hours each.
Another alternative would be to be proactive about token expiration. If your code know the token duration and the time at which it acquired the token, I would suggest to call a method before calling a method that uses the token (such as S3's getObject). That method you check if the token are soon to expire and proactively refresh them. Pseudo code would be like
function refreshToken() {
return new Promise( (resolve, reject) => {
// XX depends how long is your S3 getObject call
if (token_acquisition_time + token_duration <= now() + xx minutes) {
// refresh token
sts.assumeRole(...).promise().then(resolve());
} else {
resolve();
}
});
}
...
refreshToken.then(s3.getObject(...));
The AWS SDK can handle refreshing the credentials for you. For example:
const credentials = new ChainableTemporaryCredentials({
params: {
RoleArn: "optional-role-arn",
RoleSessionName: `required-parameter-${Date.now()}`
}
})
const s3 = new AWS.S3({credentials})
Now AWS SDK will refresh the tokens behind the scenes without any action from caller of s3.
For more information, please see AWSK SDK Documentation. Refresh is limited to validity time of the credentials used.

Convert NodeJS asynchronous code to Spring Project Reactor

I have below NodeJS code:
// req and resp are http request, response objects
var uri = req.getURI()
var pageView = new PageView(uri)
var token = req.token
if (token) {
UserRepository.findByToken(token, function(notFound, user){
if(notFound) { // means user not found by specified token
var newUser = new User('John Doe')
user.foo = 'some value'
processUser(newUser, pageView)
} else { // user found by token
user.foo = 'some value'
processUser(user, pageView)
}
})
} else { // token does not exist
token = new Token('some value')
resp.setToken(token)
var newUser = new User('John Doe')
user.foo = 'some value'
processUser(newUser, pageView)
}
processUser(user, pageView) {
PageViewRepositiry.save(pageView, function(error, savedPageView){
if(error) {
throw 'error'
}
user.pageViews.push(savedPageView)
// save the modified savedUser
UserRepository.save(user , function(error, savedUser){
})
})
}
It uses Repository pattern as abstraction over database layer (same as the Repository pattern in Spring applications).
Basically it finds user by incoming token (from http req object). If user is found then updates user entity and adds the saved pageView entity and saves the modified user. If user is not found by token then it creates a new User, updates the user with saved pageView, saves the user.
How the same code will be written in Spring Project Reactor (Flux) ?
Is it possible to solve this problem without using block()? Ideally I would like a solution that does not use block().
First of all, you have some logic to generate a token if a token isn't present. For example:
private Mono<String> getToken(String token) {
return Mono
.just(token)
.switchIfEmpty(Mono.just("some token"));
}
In this case, it's a bit overkill to use switchIfEmpty for this, but I assume your process to generate a token is a bit more complex, otherwise you could have worked with Optional<String> in stead (eg. token.orElse("some token")).
Additionally, we also have some logic to either find the user by its token, or create a new user if there is no user by the given token:
private Mono<User> findUserByToken(String token) {
return userRepository
.findByToken(token)
.switchIfEmpty(userRepository.save(new User("John Doe", token)));
}
Now that we have these methods, we can create a PageView and use these methods along the way. The reason I start with creating a PageView is because that's the first "constant" in the entire token, regardless of whether there is a token/user found:
return Mono
.just(new PageView(uri))
.flatMap(pageViewRepository::save)
.flatMap(pageView -> getToken(token)
.flatMap(this::findUserByToken)
.doOnNext(user -> user.setFoo("foo"))
.doOnNext(user -> user.getPageView().add(pageView)))
.flatMap(userRepository::save)
.map(User::getToken);
Now, since you need the token to add to the response, and I figured out that the token is part of the User object somehow (otherwise UserRepository.findByToken() wouldn't work?), it would be easier to just use User::getToken at the end to retrieve the token to pass to the response.
Be aware though, the repository pattern does work properly with Spring, but there is only reactive support for MongoDB, Cassandra, Couchbase and Redis. Other than that there's also reactive support for PostgreSQL through rdbc, but I don't think Spring data has support for that.

Is there a way to prevent users from editing the local storage session?

I am creating a relational blog where I make use of ember_simple_auth:session to store the session like
{"authenticated":{"authenticator":"authenticator:devise","token":"rh2f9iy7EjJXESAM5koQ","email":"user#example.com","userId":1}}
However, on the developer tools on Chrome (and possibly on other browsers), it is quite easy to edit the email and userId in order to impersonate another user upon page reload.
EDIT #1
From the conversation with Joachim and Nikolaj, I now realized that the best way to tackle this problem is to probe the localStorage authenticity every time I need it (which is only on page reload) instead of attempting to prevent edits.
In order to validate authenticity, I create a promise that must be solved before the AccountSession can be used. The promise serverValidation() requests to create a token model with the current localStorage info, and when the server gets it, it validates the info and responds 200 with a simple user serialization with type as token if the information is legit. You can check more info on the Source Code.
Session Account
import Ember from 'ember';
const { inject: { service }, RSVP } = Ember;
export default Ember.Service.extend ({
session: service('session'),
store: service(),
serverValidation: false,
// Create a Promise to handle a server request that validates the current LocalStorage
// If valid, then set SessionAccount User.
loadCurrentUser() {
if (!Ember.isEmpty(this.get('session.data.authenticated.userId'))) {
this.serverValidation().then(() => {
return new RSVP.Promise((resolve, reject) => {
const userId = this.get('session.data.authenticated.userId');
// Get User to Session-Account Block
if(this.get('serverValidation') === true) {
return this.get('store').find('user', userId).then((user) => {
this.set('user', user);
resolve();
}).catch((reason) => {
console.log(reason.errors);
var possible404 = reason.errors.filterBy('status','404');
var possible500 = reason.errors.filterBy('status','500');
if(possible404.length !== 0) {
alert('404 | Sign In Not Found Error');
this.get('session').invalidate();
}
else if(possible500.length !== 0) {
alert('500 | Sign In Server Error');
this.get('session').invalidate();
}
reject();
});
}
else{
alert('Session for Server Validation failed! Logging out!');
this.get('session').invalidate();
resolve();
}
});
});
} else {
// Session is empty...
}
},
serverValidation() {
return new RSVP.Promise((resolve) => {
var tokenAuthentication = this.get('store').createRecord('token', {
id: this.get('session.data.authenticated.userId'),
email: this.get('session.data.authenticated.email'),
authenticity_token: this.get('session.data.authenticated.token'),
});
tokenAuthentication.save().then(() => {
this.set('serverValidation',true);
console.log('Server Validation complete with 200');
resolve();
}).catch((reason) => {
this.set('serverValidation',false);
resolve();
});
});
}
});
Token Controller
# Users Controller: JSON response through Active Model Serializers
class Api::V1::TokensController < ApiController
respond_to :json
def create
if token_by_id == token_by_token
if token_by_email == token_by_id
render json: token_by_id, serializer: TokenSerializer, status: 200
else
render json: {}, status: 404
end
else
render json: {}, status: 404
end
end
private
def token_by_id
User.find(user_params[:id])
end
def token_by_email
User.find_by(email: user_params[:email])
end
def token_by_token
User.find_by(authentication_token: user_params[:authenticity_token])
end
def user_params
ActiveModelSerializers::Deserialization.jsonapi_parse!(params.to_unsafe_h)
end
end
There is no way to prevent a user from editing the content of his local storage, session storage, or cookies.
But this should not worry you. The user is identified through the value of the token. The token is generated and sent to him by the authenticator when he logs in. To impersonate another user by editing the session data he would have to know that the other user is logged in, and know the token of that user.
Token is already signed on the server side, a standard JWT mechanism.
Having said that, there can be a couple of ways to check tempering in local storage:
Generate a token the way you already do.
Generate a random secret key to be kept on the server.
Generate a corresponding HMAC using this secret key.
Send the token + HMAC to the user.
When the user sends you this token, first check if HMAC is correct, if not then reject the token right away.
If HMAC is correct, validate the token the way you already do.
Another way:
Along with the token, a HMAC checksum too can be stored separately, and when sent back to the server by the client, check if checksum matches.

Resources