Promise in JavaScript is not running the supertest request - javascript

I thought I setup this Promise to attempt to perform a login POST request for a RESTful service using supertest. However, it is not working for me.
Here is the code:
'use strict';
/*
* loginRequest.js
*
* Perform a basic login using supplied values. Only tests for Response Code 200
*/
var base64_encode = require('Base64').btoa
, request = require('supertest')
, VerifyUrl = require('./VerifyUrl');
module.exports = loginRequest;
/**
* Perform a basic login and returns a Promise. It resolves with the response or rejects with the error.
*
* #param username {string} The username.
* #param password {string} The password.
* #param url {string} The url under test. If not supplied, assumes 'http://localhost:9000/'
* #returns {Promise} The Promise container of the login request which resolves with the POST response or rejects with the error.
*/
function loginRequest(username, password, url) {
return new Promise(function (resolve, reject) {
url = VerifyUrl(url); // Provides a default url if non is supplied, ensures a trailing '/'.
var authorization = "Basic " + base64_encode(username + ':' + password); // Yes, I know, not secure. The app isn't yet production ready.
console.log("creds = " + username + ':' + password);
console.log("auth = " + authorization);
request(url)
.post('api/users/login')
.set('authorization', authorization)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200)
.expect(function() {console.log("Made the expectation.")})
.end(function (err, res) {
console.log("Reached the login .end method.");
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
}
The output from this is:
creds = autoTest1465828536379:4ut0T3st
auth = Basic YXV0b1Rlc3QxNDY1ODI4NTM2Mzc5OjR1dDBUM3N0
When I run it through the debugger, with breakpoints on each request method, it stops at the .set, but does not run the .expect or .end methods. What am I missing or not understanding?
Additional info. From the spec script, I am using the above code as follows:
var login = require('loginRequest');
var username = 'autoTest' + Date.now();
var password = '4ut0T3st';
login(username, password, 'http://localhost:9000')
.then(function(loginResponse) {
// do stuff with the response
})
.catch(function(err) {
// do stuff with the err
});
But it never reaches the .then or .catch of this call. (The output is as described above.

Related

How to handle DirectLine connection errors

we run a bot using bot framework and connect to it from our website using direct line in JS. We get a token from a custom API endpoint and we store the token in sessionStorage. Then we connect to the bot using
directLine = await window.WebChat.createDirectLine({
token,
conversationId,
watermark: "0"
});
Everything is working fine, but when I leave the page open for too long the token in the sessionStorage expires. A page refresh or navigating to a different page causes a 403 error inside the createDirectLine method. Resulting in a chat bot that can't connect for as long as the sessionStorage holds that token. This behavior is not a surprise to me, but I don't know how to handle this.
What I want is to simply clear the sessionStorge, request a new token and start a new conversation when this happens. But I don't know how to do that. How do I get the 403 error from the createDirectLine method? Or is there a way to validate the token upfront?
I already tried putting a try/catch block around the createDirectLine method, but the 403 error did not show up in the catch.
Thanks in advance!
This solution is only to address the 403 error from occurring since the token expires (in 30 minutes I think). A better solution is to store the conversationId with the token and get a new token with that. Check official bot service documentation for that.
// to shorten code, we store in sessionStorage as separate items.
const expirationDuration = 1000 * 60 * 30; // 30 minutes
const currentTime = new Date().getTime();
const timeTokenStored = sessionStorage.getItem("timeTokenStored") || currentTime;
// if token is stored over 30 minutes ago, ignore it and get a new one. Otherwise, use it.
if ((currentTime - timeTokenStored) > expirationDuration) {
const res = await fetch('https://<yourTokenEndpoint>', { method: 'POST' });
const { token } = await res.json();}
const currentTime = new Date().getTime();
sessionStorage.setItem("timeTokenStored", currentTime);
sessionStorage.setItem('token', token);
else {
const token = sessionStorage.getItem("token")
}
While your're at it, you might as well store it in localStorage. This way, your bot will follow the user.
I have found the solution. We can check that a token is valid by refreshing it. If refreshing causes an error, the token is not valid anymore. If refreshing succeeds, the toke will be valid for another hour.
So we added (resusable) a function to our back end to refresh the token using https://directline.botframework.com/v3/directline/tokens/refresh.
And we changed the front end code to call our new refresh function.
Front end code:
// Gets a new token from the cloud.
async function requestToken() {
if (!sessionStorage['webchatToken']) {
const res = await fetch('https://' + serviceName + '.azurewebsites.net/api/token');
// If the request was succesfull, store the token and userId.
if (res.status == 200) {
const jsonResult = await res.json();
sessionStorage['webchatToken'] = jsonResult.token;
sessionStorage['webchatUserId'] = jsonResult.userId;
console.log(`Got token from cloud`);
// refresh the token every 15 minutes.
setTimeout(() => {
refreshToken();
}, 60000 * 15); // 15 minutes
}
// If the request was not succesfull, retry.
else {
console.log(`Tried to get token, but goterror ` + res.status + `. Retrying.`);
await requestToken();
}
}
// If there is already a token in storage, refresh the existing one instead of requesting a new one.
else {
console.log(`Got token from sessionStorage`);
await refreshToken();
}
}
// Refreshes an existing token so it doesn't expire.
async function refreshToken() {
// Refresh the token if it exists in storage.
if (sessionStorage['webchatToken']) {
const res = await fetch('https://' + serviceName + '.azurewebsites.net/api/token/refresh?token=' + sessionStorage['webchatToken'],
{
method: 'POST'
});
// If refresh was succesfull we are done.
if (res.status == 200) {
console.log(`Refreshed token`);
}
// If refresh was not succesfull, clear the token from storage and request a new one. The token is probably expired.
else {
console.log(`Tried to refresh token, but got error ` + res.status + `. Requesting new token.`);
sessionStorage.clear();
await requestToken();
}
}
// If there is no token in storage, request a new token.
else {
console.log(`Tried to refresh token, but token is not defined. Requesting new token.`);
sessionStorage.clear();
await requestToken();
}
}
Back end code:
[HttpGet]
[Route("api/token")]
public async Task<ObjectResult> GetToken()
{
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Post,
$"https://directline.botframework.com/v3/directline/tokens/generate");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _configuration.DirectLineKey);
var userId = $"dl_{Guid.NewGuid()}";
request.Content = new StringContent(
JsonConvert.SerializeObject(new { User = new { Id = userId } }),
Encoding.UTF8,
"application/json");
var response = await client.SendAsync(request);
string token = String.Empty;
int expiresIn = 0;
if (response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
token = JsonConvert.DeserializeObject<DirectLineToken>(body).token;
expiresIn = JsonConvert.DeserializeObject<DirectLineToken>(body).expires_in;
}
return Ok(new { token, userId, expiresIn });
}
[HttpPost]
[Route("api/token/refresh/")]
public async Task<ObjectResult> RefreshToken(string token)
{
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Post,
$"https://directline.botframework.com/v3/directline/tokens/refresh");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.SendAsync(request);
token = String.Empty;
int expiresIn = 0;
if (response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
token = JsonConvert.DeserializeObject<DirectLineToken>(body).token;
expiresIn = JsonConvert.DeserializeObject<DirectLineToken>(body).expires_in;
}
if (string.IsNullOrEmpty(token))
return Problem("Token incorrect");
return Ok(new { token, expiresIn });
}
I hope posting this may be useful to somebody.

How to enable CORS in an Azure App Registration when used in an OAuth Authorization Flow with PKCE?

I have a pure Javascript app which attempts to get an access token from Azure using OAuth Authorization Flow with PKCE.
The app is not hosted in Azure. I only use Azure as an OAuth Authorization Server.
//Based on: https://developer.okta.com/blog/2019/05/01/is-the-oauth-implicit-flow-dead
var config = {
client_id: "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx",
redirect_uri: "http://localhost:8080/",
authorization_endpoint: "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize",
token_endpoint: "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token",
requested_scopes: "openid api://{tenant-id}/user_impersonation"
};
// PKCE HELPER FUNCTIONS
// Generate a secure random string using the browser crypto functions
function generateRandomString() {
var array = new Uint32Array(28);
window.crypto.getRandomValues(array);
return Array.from(array, dec => ('0' + dec.toString(16)).substr(-2)).join('');
}
// Calculate the SHA256 hash of the input text.
// Returns a promise that resolves to an ArrayBuffer
function sha256(plain) {
const encoder = new TextEncoder();
const data = encoder.encode(plain);
return window.crypto.subtle.digest('SHA-256', data);
}
// Base64-urlencodes the input string
function base64urlencode(str) {
// Convert the ArrayBuffer to string using Uint8 array to convert to what btoa accepts.
// btoa accepts chars only within ascii 0-255 and base64 encodes them.
// Then convert the base64 encoded to base64url encoded
// (replace + with -, replace / with _, trim trailing =)
return btoa(String.fromCharCode.apply(null, new Uint8Array(str)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
// Return the base64-urlencoded sha256 hash for the PKCE challenge
async function pkceChallengeFromVerifier(v) {
const hashed = await sha256(v);
return base64urlencode(hashed);
}
// Parse a query string into an object
function parseQueryString(string) {
if (string == "") { return {}; }
var segments = string.split("&").map(s => s.split("="));
var queryString = {};
segments.forEach(s => queryString[s[0]] = s[1]);
return queryString;
}
// Make a POST request and parse the response as JSON
function sendPostRequest(url, params, success, error) {
var request = new XMLHttpRequest();
request.open('POST', url, true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.onload = function () {
var body = {};
try {
body = JSON.parse(request.response);
} catch (e) { }
if (request.status == 200) {
success(request, body);
} else {
error(request, body);
}
}
request.onerror = function () {
error(request, {});
}
var body = Object.keys(params).map(key => key + '=' + params[key]).join('&');
request.send(body);
}
function component() {
const element = document.createElement('div');
const btn = document.createElement('button');
element.innerHTML = 'Hello'+ 'webpack';
element.classList.add('hello');
return element;
}
(async function () {
document.body.appendChild(component());
const isAuthenticating = JSON.parse(window.localStorage.getItem('IsAuthenticating'));
console.log('init -> isAuthenticating', isAuthenticating);
if (!isAuthenticating) {
window.localStorage.setItem('IsAuthenticating', JSON.stringify(true));
// Create and store a random "state" value
var state = generateRandomString();
localStorage.setItem("pkce_state", state);
// Create and store a new PKCE code_verifier (the plaintext random secret)
var code_verifier = generateRandomString();
localStorage.setItem("pkce_code_verifier", code_verifier);
// Hash and base64-urlencode the secret to use as the challenge
var code_challenge = await pkceChallengeFromVerifier(code_verifier);
// Build the authorization URL
var url = config.authorization_endpoint
+ "?response_type=code"
+ "&client_id=" + encodeURIComponent(config.client_id)
+ "&state=" + encodeURIComponent(state)
+ "&scope=" + encodeURIComponent(config.requested_scopes)
+ "&redirect_uri=" + encodeURIComponent(config.redirect_uri)
+ "&code_challenge=" + encodeURIComponent(code_challenge)
+ "&code_challenge_method=S256"
;
// Redirect to the authorization server
window.location = url;
} else {
// Handle the redirect back from the authorization server and
// get an access token from the token endpoint
var q = parseQueryString(window.location.search.substring(1));
console.log('queryString', q);
// Check if the server returned an error string
if (q.error) {
alert("Error returned from authorization server: " + q.error);
document.getElementById("error_details").innerText = q.error + "\n\n" + q.error_description;
document.getElementById("error").classList = "";
}
// If the server returned an authorization code, attempt to exchange it for an access token
if (q.code) {
// Verify state matches what we set at the beginning
if (localStorage.getItem("pkce_state") != q.state) {
alert("Invalid state");
} else {
// Exchange the authorization code for an access token
// !!!!!!! This POST fails because of CORS policy.
sendPostRequest(config.token_endpoint, {
grant_type: "authorization_code",
code: q.code,
client_id: config.client_id,
redirect_uri: config.redirect_uri,
code_verifier: localStorage.getItem("pkce_code_verifier")
}, function (request, body) {
// Initialize your application now that you have an access token.
// Here we just display it in the browser.
document.getElementById("access_token").innerText = body.access_token;
document.getElementById("start").classList = "hidden";
document.getElementById("token").classList = "";
// Replace the history entry to remove the auth code from the browser address bar
window.history.replaceState({}, null, "/");
}, function (request, error) {
// This could be an error response from the OAuth server, or an error because the
// request failed such as if the OAuth server doesn't allow CORS requests
document.getElementById("error_details").innerText = error.error + "\n\n" + error.error_description;
document.getElementById("error").classList = "";
});
}
// Clean these up since we don't need them anymore
localStorage.removeItem("pkce_state");
localStorage.removeItem("pkce_code_verifier");
}
}
}());
In Azure I only have an App registration (not an app service).
Azure App Registration
The first step to get the authorization code works.
But the POST to get the access token fails. (picture from here)
OAuth Authorization Code Flow with PKCE
Access to XMLHttpRequest at
'https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token' from
origin 'http://localhost:8080' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource.
Where in Azure do I configure the CORS policy for an App Registration?
Okay, after days of banging my head against the stupidity of Azure's implementation I stumbled upon a little hidden nugget of information here: https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-browser#prerequisites
If you change the type of the redirectUri in the manifest from 'Web' to 'Spa' it gives me back an access token! We're in business!
It breaks the UI in Azure, but so be it.
You should define the internal url with your local host address.
https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/application-proxy-understand-cors-issues
When I first posted, the Azure AD token endpoint did not allow CORS requests from browsers to the token endpoint, but it does now. Some Azure AD peculiarities around scopes and token validation are explained in these posts and code in case useful:
Code Sample
Blog Post

How to keep multiple requests separate in Nodejs / Expressjs

I am developing a NodeJS / ExpressJS application on my computer. I have node running locally. I have a single page web app. When it needs information it makes ajax requests with jQuery.
The problem is when I have multiple requests for different sets of data.
Node/express will start processing the first request and then the second request comes in before the first request has been fulfilled, it sends the response to the second request from the first request instead of sending it to the first request like it is suppose to. If I put a pause ( using an alert ) in my app so that is slows it down so the next request doesn't get sent until the first request was fulfilled, everything works fine.
I don't understand why this is happening. I thought node / express was suppose to be able to handle this and keep the requests separate.
Also, I get a "Can't set headers after they are sent" error in node because it is apparently merging the requests....
Here is what happens
ajax request 1 -> server
ajax request 2 -> server
ajax request 3 -> server
server -> request1 ( no response )
server -> request2 ( request 1's data)
server -> request3 ( request 2's data)
server for request3 --> error: header's already sent
I am using Google Chrome 63 with jQuery 3.3.1 on a Windows 10 machine running Node 8.9.4 and Express 4.16.2
My work-around is to chain the ajax requests so that each request doesn't get called until the prior request has received a response from the server. But I shouldn't have to do that...
Here is the relevant server code:
var mysql = require("mysql");
var express = require('express');
var app = express();
var DEBUG = true;
var request = null;
var response = null;
var currentDataRowParser = null;
var con = mysql.createConnection(config);
function ParseMySqlRowData(rowData)
{
if (DEBUG) console.log("ParseMySqlRowData");
return JSON.stringify(rowData);
}
var ParseMySqlRowsDatatoResponse = function (err, rows)
{
if (DEBUG) console.log("ParseMySqlRowsDatatoResponse");
var MySQLRows;
try
{
if (!err)
{
MySQLRows = "[";
for (var i = 0; i < rows.length; i++)
{
if (i > 0)
MySQLRows += ", ";
MySQLRows += currentDataRowParser(rows[i]);
}
MySQLRows += "]";
if (DEBUG) console.log("write rows");
if (DEBUG) console.log(MySQLRows);
response.send(MySQLRows);
response.end();
}
}
catch (ex)
{
if (DEBUG) console.log("ParseMySQLRowsDatatoResponse: ERROR");
if (DEBUG) console.log(ex);
}
};
var GetQueryData = function (query, dataRowParser, parseDataCallbackFunction)
{
if (DEBUG) console.log("GetQueryData");
currentDataRowParser = dataRowParser;
if (parseDataCallbackFunction == null || parseDataCallbackFunction == undefined)
parseDataCallbackFunction = ParseDataCallback;
try
{
if (DEBUGQUERY)
{
console.log("before query");
console.log(con.query(query, parseDataCallbackFunction));
console.log("after query");
console.log(query.sql);
DEBUGQUERY = false;
}
else
{
con.query(query, parseDataCallbackFunction);
}
}
catch (ex)
{
console.log(ex);
}
};
app.post('/getdata', function(req, res)
{
request = req;
response = res;
var query;
switch (request.body.loaddata)
{
case "dataset1":
query = "SELECT * FROM table1 WHERE key='" + request.body.key + "'";
GetQueryData(query,ParseMySqlRowData,ParseMySqlRowsDatatoResponse);
break;
case "dataset2":
query = "SELECT * FROM table2 WHERE key='" + request.body.key + "'";
GetQueryData(query,ParseMySqlRowData,ParseMySqlRowsDatatoResponse);
break;
case "dataset3":
query = "SELECT * FROM table3 WHERE key='" + request.body.key + "'";
GetQueryData(query,ParseMySqlRowData,ParseMySqlRowsDatatoResponse);
break;
}
};
You cannot store req and res in global or module-level variables. When a second request comes in, it will immediately overwrite your globals and it will mix up the data for the various requests.
Does't node separate each request instance?
Yes, there is a separate request instance, but not a separate global or module-level namespace. So, when you assign the req into the global space, you are overwriting the previous one and your code will then use the wrong one.
It is very helpful to have the request and response as a global variable. Otherwise I would have to be passing them all over the place.
You HAVE to pass them to lower level functions that need them. That's how you keep each request separate from the others. ANY function that needs to operate on req or res should be passed those variables so it knows exactly which ones to operate on.
node.js has a shared global and module-level namespace. So, all requests in flight at the same time use that same namespace. The ONLY data that should ever be stored there is data that you specifically want to be shared between requests (such as session state, for example). Individual request or response objects should never be stored in a shared variable.
A more common way to code your type of code is to call a function like your GetQueryData() and have it return the data (likely via a promise) and then you send the response in the original request handler. Then, you don't have to pass req or res down multiple levels at all. Your helper functions just fetch data. The request handlers fetch data, then send the response. It's often a better encapsulation of functionality.
Here's one way to restructure your code along the lines described above.
GetQueryData() returns a promise that fulfills with the data
ParseMySqlRowsData() just returns a parsed result or null if a parsing error
app.post() just gets the data (via a promise) and then sends the appropriate response.
There's no global req or res and there's no need to pass them anywhere.
Here's the code:
var mysql = require("mysql");
var express = require('express');
var app = express();
var DEBUG = true;
var currentDataRowParser = null;
var con = mysql.createConnection(config);
function ParseMySqlRowData(rowData) {
if (DEBUG) console.log("ParseMySqlRowData");
return JSON.stringify(rowData);
}
var ParseMySqlRowsData = function(err, rows) {
if (DEBUG) console.log("ParseMySqlRowsDatatoResponse");
var MySQLRows;
try {
if (!err) {
MySQLRows = "[";
for (var i = 0; i < rows.length; i++) {
if (i > 0)
MySQLRows += ", ";
MySQLRows += currentDataRowParser(rows[i]);
}
MySQLRows += "]";
if (DEBUG) console.log("write rows");
if (DEBUG) console.log(MySQLRows);
return MySQLRows;
}
} catch (ex) {
if (DEBUG) console.log("ParseMySQLRowsDatatoResponse: ERROR");
if (DEBUG) console.log(ex);
return null;
}
};
var GetQueryData = function(query, dataRowParser, parseDataCallbackFunction) {
return new Promise((resolve, reject) =>{
if (DEBUG) console.log("GetQueryData");
let currentDataRowParser = dataRowParser;
if (parseDataCallbackFunction == null || parseDataCallbackFunction == undefined)
parseDataCallbackFunction = ParseDataCallback;
try {
if (DEBUGQUERY) {
console.log("before query");
console.log(con.query(query, parseDataCallbackFunction));
console.log("after query");
console.log(query.sql);
DEBUGQUERY = false;
} else {
con.query(query, function(err, rows) {
if (err) {
reject(err);
} else {
let result = parseDataCallbackFunction(rows);
if (result) {
resolve(result);
} else {
reject(new Error("ParseMySqlRowsData error"));
}
}
});
}
} catch (ex) {
console.log(ex);
reject(new Error("GetQueryData error"));
}
});
};
app.post('/getdata', function(req, res) {
var query;
let p;
switch (request.body.loaddata) {
case "dataset1":
query = "SELECT * FROM table1 WHERE key='" + request.body.key + "'";
p = GetQueryData(query, ParseMySqlRowData, ParseMySqlRowsData);
break;
case "dataset2":
query = "SELECT * FROM table2 WHERE key='" + request.body.key + "'";
p = GetQueryData(query, ParseMySqlRowData, ParseMySqlRowsData);
break;
case "dataset3":
query = "SELECT * FROM table3 WHERE key='" + request.body.key + "'";
p = GetQueryData(query, ParseMySqlRowData, ParseMySqlRowsData);
break;
default:
p = Promise.reject(new Error("Invalid request.body.loaddata"));
break;
}
p.then(data => {
res.send(data);
}).catch(err => {
console.log(err);
res.sendStatus(500);
});
};
P.S. I see you still have a module-level variable you should not have: currentDataRowParser. That needs to be replaced by passing the appropriate parser to ParseMySqlRowsData() as an argument, not using a module-level shared variable. I will leave that as an excercise for you to fix. Shared variables for operating on a specific request state are a bad idea in ANY server programming and specifically in node.js programming. Keep your request-specific state in your function arguments or on the req or res object itself. That's how you prevent overwritting the data from one request with the data from another while they are being processed.

Mongoose "schema method" callback not working

I'm new to mongoose and node.js. I try to follow this tutorial: https://scotch.io/tutorials/using-mongoosejs-in-node-js-and-mongodb-applications#sample-model-for-users
In my entry point index.js, I tried to call "chenya.saltHashPassword(function(err, passwordHash)". It is actually called in user.js because user.js can print out three log messages; however, there is no log message for this method call at all in index.js. In contrast, the save method can print out log message indicating successful saving.:
//Lets load the mongoose module in our program
var mongoose = require('mongoose');
//Lets connect to our database using the DB server URL.
mongoose.connect('mongodb://localhost:27017/server_naturis');
// if our user.js file is at app/models/user.js
var User = require('./user');
// create a new user called Chenya
var chenya = new User({
userName: 'Chenya',
email: 'chenya#gmail.com',
password: 'Chenya'
});
// call the custom method. hash the password
chenya.saltHashPassword(function(err, passwordHash) { // NOT CALLED!
if (err) {
console.log('chenya.saltHashPassword: ' + err);
} else {
this.password = passwordHash;
console.log('Your hashed password is ' + passwordHash);
}
});
// call the built-in save method to save to the database
chenya.save(function(err) { // CALLED!
if (err) {
console.log('chenya.save: ' + err);
} else {
console.log('User saved successfully!');
}
});
In my user.js, I have the schema function "userSchema.methods.saltHashPassword":
// grab the things we need
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// Require the crypto module for password hash
'use strict';
var crypto = require('crypto');
// create a schema
var userSchema = new Schema({
userName: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
});
// add a schema method
/**
* generates random string of characters i.e salt
* #function
* #param {number} length - Length of the random string.
*/
var genRandomString = function(length){
return crypto.randomBytes(Math.ceil(length/2))
.toString('hex') /** convert to hexadecimal format */
.slice(0,length); /** return required number of characters */
};
/**
* hash password with sha512.
* #function
* #param {string} password - List of required fields.
* #param {string} salt - Data to be validated.
*/
var sha512 = function(password, salt){
var hash = crypto.createHmac('sha512', salt); /** Hashing algorithm sha512 */
hash.update(password);
var value = hash.digest('hex');
return {
salt:salt,
passwordHash:value
};
};
/**
* a function that will use the above function
* to generate the hash that should be stored
* in the database as user’s password.
*/
userSchema.methods.saltHashPassword = function() {
var salt = genRandomString(16); /** Gives us salt of length 16 */
var passwordData = sha512(this.password, salt);
console.log('UserPassword = '+ this.password);
console.log('Passwordhash = '+ passwordData.passwordHash);
console.log('\nSalt = '+ passwordData.salt);
return passwordData.passwordHash;
}
// the schema is useless so far
// we need to create a model using it
var User = mongoose.model('User', userSchema);
// make this available to our users in our Node applications
module.exports = User;
Terminal:
UserPassword = Chenya
Passwordhash = 5bb5bf2181e2c713bae1eb49d1f3646b23db839368d38c33951774c92cec39a3c4b855aea30875e72cce6f271bdbdb27de8976c9316df09d086435b6c5629548
Salt = a88384d072b720de
(node:11717) DeprecationWarning: Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html
User saved successfully!
You're not passing in a callback parameter into userSchema.methods.saltHashPassword but treating the function as if you did.
Change userSchema.methods.saltHashPassword to:
userSchema.methods.saltHashPassword = function(callback) { // <-- Add callback param
var salt = genRandomString(16); /** Gives us salt of length 16 */
var passwordData = sha512(this.password, salt);
console.log('UserPassword = '+ this.password);
console.log('Passwordhash = '+ passwordData.passwordHash);
console.log('\nSalt = '+ passwordData.salt);
// Your function that you passed in is called here
callback(null, passwordData.passwordHash);
}
The reason that you're callback was not called in saltHashPassword but was called in save is because Mongoose defines that method to require a callback function that takes in a parameter for an error and the actual return value.
When an error occurs, it is expected that the callback will have error handling defined, which is good practice and why you see tutorials suggesting you do the same. When you define your own method for a Schema you no longer have that and have to set it up yourself.
So as you can see in the function above, that's what happened. Now you're callback is passed in as a parameter calling it with callback(null, passwordData.passwordHash) will make it execute. If you ever had an error occur, you could then save it as variable, e.g. err and pass that into your function as callback(err, null)
Going back to salts. I haven't read your tutorial but it's important that you save them in the database along with your user data so that passwords can be verified when a user logs in.
Good resource here:
Password Hashing add salt + pepper or is salt enough?
You need the salt to generate the same hashed password you store in the database. If you don't have access to that salt, you can't know if the password you're given would generate the same hash.

You are not authorized to access this page Odesk api node.js

/**
* Example of usage oDeskAPI
*
* #package oDeskAPI
* #since 09/22/2014
* #copyright Copyright 2014(c) oDesk.com
* #author Maksym Novozhylov <mnovozhilov#odesk.com>
* #license oDesk's API Terms of Use {#link https://developers.odesk.com/api-tos.html}
*/
var config = {
'consumerKey' : '571a5ff21bded617e499965f9cf013a0',
'consumerSecret' : 'e3df4989efed7761',
// 'accessToken' : 'xxxxxxxx', // assign if known
// 'accessSecret' : 'xxxxxxxx', // assign if known
'debug' : false
};
//var oDeskApi = require('../') // uncomment to use inside current package/sources
var oDeskApi = require('odesk-api') // use if package is installed via npm
// , Auth = require('../lib/routers/auth').Auth // uncomment to use inside current package/sources
, Auth = require('odesk-api/lib/routers/auth').Auth // use if package is installed via npm
, rl = require('readline');
// you can use your own client for OAuth routine, just identify it here
// and use as a second parameter for oDeskApi constructor (see the example of usage below)
// note: your client must support the following methods:
// 1. getAuthorizationUrl - gets request token/secret pair, creates and returns
// authorization url, based on received data
// 2. getAccessToken(requestToken, requestTokenSecret, verifier, callback) -
// requests access token/secret pair using known request token/secret pair and verifier
// 3. setAccessToken(token, secret, callback) - sets known access token/secret pair
// 4. get|post|put|delete(path, data, callback) - for GET, POST, PUT and DELETE methods respectively
// 5. setEntryPoint(entryPoint) - allows setup different entry point for base url
//
// var MyClient = require('../lib/myclient').MyClient;
//
// by default predefined lib/client.js will be used that works with other odesk oauth library
// a function to get access token/secret pair
function getAccessTokenSecretPair(api, callback) {
// get authorization url
api.getAuthorizationUrl('http://localhost/complete', function(error, url, requestToken, requestTokenSecret) {
if (error) throw new Error('can not get authorization url, error: ' + error);
debug(requestToken, 'got a request token');
debug(requestTokenSecret, 'got a request token secret');
// authorize application
var i = rl.createInterface(process.stdin, process.stdout);
i.question('Please, visit an url ' + url + ' and enter a verifier: ', function(verifier) {
i.close();
process.stdin.destroy();
debug(verifier, 'entered verifier is');
// get access token/secret pair
api.getAccessToken(requestToken, requestTokenSecret, verifier, function(error, accessToken, accessTokenSecret) {
if (error) throw new Error(error);
debug(accessToken, 'got an access token');
debug(accessTokenSecret, 'got an access token secret');
callback(accessToken, accessTokenSecret);
});
});
});
};
// get my data
function getUserData(api, callback) {
// make a call
var auth = new Auth(api);
auth.getUserInfo(function(error, data) {
// check error if needed and run your own error handler
callback(error, data);
});
}
(function main() {
// uncomment only if you want to use your own client
// make sure you know what you're doing
// var client = new MyClient(config);
// var api = new oDeskApi(null, client);
// use a predefined client for OAuth routine
var api = new oDeskApi(config);
if (!config.accessToken || !config.accessSecret) {
// run authorization in case we haven't done it yet
// and do not have an access token-secret pair
getAccessTokenSecretPair(api, function(accessToken, accessTokenSecret) {
debug(accessToken, 'current token is');
// store access token data in safe place!
// get my auth data
getUserData(api, function(error, data) {
debug(data, 'response');
console.log('Hello: ' + data.auth_user.first_name);
});
});
} else {
// setup access token/secret pair in case it is already known
api.setAccessToken(config.accessToken, config.accessSecret, function() {
// get my auth data
getUserData(api, function(error, data) {
debug(data, 'response');
// server_time
console.log('Hello: ' + data.auth_user.first_name);
});
});
}
})();
This is my example.js Currently Its giving the response url of odesk .Just will i try to access that url Error comes (You are not authorized to access this page.) I have installed the app successfully by using node.js .I have the main issue in configuration Right .
You have to provide the same call back Url Which you have provide Odesk While making app your Here
api.getAuthorizationUrl('http://localhost/complete', function(error, url, requestToken, requestTokenSecret) {
if (error) throw new Error('can not get authorization url, error: ' + error);

Categories

Resources