UCWA Lync authentication - 500 web ticket is invalid - node.js

I'm trying to create a simple client for Lync using Nodejs.
Base on http://ucwa.lync.com/documentation/KeyTasks-CreateApplication I've made someting like this.
It works until last step #9 when I should register my app with UCWA. Server responds with code 500 and silly explanation
There is a problem with the resource you are looking for, and it cannot be displayed
And headers
x-ms-diagnostics': '28032;source="mysource";reason="The web ticket is invalid."'
var http = require('request-promise');
var lync = {};
lync.setup = function(email, password){
var self = this;
var hostname = email.split('#');
this.username = email;
//discover urls
return http.get('http://lyncdiscover.'+hostname[1])
.then(function(d) {
var parsed = JSON.parse(d);
self.urls = {
self: parsed._links.self.href,
user: parsed._links.user.href,
xframe: parsed._links.xframe.href
};
return http.get(self.urls.user);
}).catch(function(err){
if(err.statusCode == 401){
var toParse = err.response.headers['www-authenticate'];
var Oauth = toParse.match(/https:\/\/[\d\w\./_-]*/i)[0];
var loginPost = {
grant_type: 'password',
username: email,
password: password
};
return http.post(Oauth, {form:loginPost});
}
return false
}).then(function(data){
var parsed = JSON.parse(data);
//setup authorization
http = http.defaults({
headers: {Authorization: parsed.token_type + ' ' + parsed.access_token}
});
//console.log(self.urls.user);
//console.log('Authorization:'+ parsed.token_type + ' ' + parsed.access_token);
return http.get(self.urls.user);
}).then(function(data){
var parsed = JSON.parse(data);
self.urls.applications = parsed._links.applications.href;
var registerApp = {
culture : "en-us",
endpointId : "2d9dc28d-4673-4035-825c-feb64be28e4e",
userAgent : "Test"
};
var r = "{'userAgent': 'NodeJs', 'endpointId' : '2d9dc28d-4673-4035-825c-feb64be28e4e', 'culture': 'en-US'}";
return http.post(self.urls.applications, {body: registerApp, json:true});
})
.then(function(data){
console.log(data);
})
.catch(function(err){
console.log(err);
return false;
});
};
//run app
lync.setup('login#domain.com', 'password').then(function(ret){
});
One key point here. It's not my server. I just have an account over there and I can login with official Lync client or Pidgin plugin.
Are there some extra steps to "allow" my app to work with UCWA?
#ShelbyZ
I can easily authorize using Oauth. I'm receiving authorization token so I'm logged in.
I'm receiving json similar to
"_links":{
"self":{"href":"link"},
"applications":{"href":"i need this"},
"xframe":{"href":"link"}
} }
Now. I need to "register my application" doing POST.
In this last step I get 500 code response.
I hope It's not related with that #Matthew Proctor said..
becouse I cannot simple administrate the server

Thank you #ShelbyZ
You were right, it was split-domain scenario. Now authorization works, and I can register my app. Also example for future generations
var http = require('request-promise');
var lync = {};
lync._authorize = function(){
var self = this;
var orgDomain = self.urls.user.match(/https:\/\/([\w\d\.]+)/i)[0];
//console.log(orgDomain);
http.get(self.urls.user).catch(function(err){
if(err.statusCode == 401){
var toParse = err.response.headers['www-authenticate'];
var Oauth = toParse.match(/https:\/\/[\d\w\./_-]+/i)[0];
var loginPost = {
grant_type: 'password',
username: self.username,
password: self.password
};
return http.post(Oauth, {form:loginPost});
}
}).then(function(data){
if(data) {
var parsed = JSON.parse(data);
//setup authorization
http = http.defaults({
headers: {Authorization: parsed.token_type + ' ' + parsed.access_token}
});
return http.get(self.urls.user);
}
}).then(function(data){
//check for split-domain scenario
var parsed = JSON.parse(data);
var domain = parsed._links.self.href.match(/https:\/\/([\w\d\.]+)/i)[0];
console.log('[1] '+orgDomain);
console.log('[2] '+domain);
if(domain!== orgDomain){
//split domain scenario
self.urls.user = self.urls.user.replace(orgDomain, domain);
http = http.defaults({
headers: {Authorization: null}
});
self._authorize();
} else { //create app
var parsed = JSON.parse(data);
self.urls.applications = parsed._links.applications.href;
var registerApp = {
culture : "en-us",
endpointId : "2d9dc28d-4673-4035-825c-feb64be28e4e",
userAgent : "NodeJs client"
};
return http.post(self.urls.applications, {body: registerApp, json:true});
}
}).then(function(app){
console.log(app);
});
};
lync.setup = function(email, password){
var self = this;
var hostname = email.split('#');
this.username = email;
this.password = password;
//discover urls
return http.get('http://lyncdiscover.'+hostname[1])
.then(function(d) {
var parsed = JSON.parse(d);
self.urls = {
self: parsed._links.self.href,
user: parsed._links.user.href,
xframe: parsed._links.xframe.href
};
return self._authorize();
});
};
//run app
lync.setup('username#domain.com', 'password');

I was getting the same error before I added my test domain to the list of Allowed Domains.
This can be updated via PowerShell, full details below:
Enabling UCWA and Configuring Allowed Domains
I've had clients see similar errors when running my code from http://localhost/, their fix was to test their code on a FQDN (ie http://testmyucwacode.mydomain.com/).

Related

WA business api nodejs

I have a problem with my nodejs code and the connection to the official whatsapp business api.
The bot connects the webhook correctly, the messages arrive to the server correctly but the code I have implemented to make it respond is not being effective, I checked the code from top to bottom but I can't find the fault.
I leave you the codes so you have more context:
whatsappController.js:
const fs = require("fs");
const myConsole = new console.Console(fs.createWriteStream("./logs.txt"));
const whatsappService = require("../services/whatsappService")
const VerifyToken = (req, res) => {
try {
var accessToken = "456E7GR****************************";
var token = req.query["hub.verify_token"];
var challenge = req.query["hub.challenge"];
if(challenge != null && token != null && token == accessToken){
res.send(challenge);
}
else{
res.status(400).send();
}
} catch(e) {
res.status(400).send();
}
}
const ReceivedMessage = (req, res) => {
try {
var entry = (req.body["entry"])[0];
var changes = (entry["changes"])[0];
var value = changes["value"];
var messageObject = value["messages"];
if(typeof messageObject != "undefined"){
var messages = messageObject[0];
var text = GetTextUser(messages);
var number = messages["from"];
myConsole.log("Message: " + text + " from: " + number);
whatsappService.SendMessageWhatsApp("The user say: " + text, number);
myConsole.log(messages);
myConsole.log(messageObject);
}
res.send("EVENT_RECEIVED");
}catch(e) {
myConsole.log(e);
res.send("EVENT_RECEIVED");
}
}
function GetTextUser(messages){
var text = "";
var typeMessage = messages["type"];
if(typeMessage == "text"){
text = (messages["text"])["body"];
}
else if(typeMessage == "interactive"){
var interactiveObject = messages["interactive"];
var typeInteractive = interactiveObject["type"];
if(typeInteractive == "button_reply"){
text = (interactiveObject["button_reply"])["title"];
}
else if(typeInteractive == "list_reply"){
text = (interactiveObject["list_reply"])["title"];
}else{
myConsole.log("sin mensaje");
}
}else{
myConsole.log("sin mensaje");
}
return text;
}
module.exports = {
VerifyToken,
ReceivedMessage
}
The second file is whatsappService which I make the connection with the api using the token and I also send the format of the message I want to send when I receive a hello for example...
const https = require("https");
function SendMessageWhatsApp(textResponse, number){
const data = JSON.stringify({
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": number,
"type": "text",
"text": {
"preview_url": false,
"body": textResponse
}
});
const options = {
host:"graph.facebook.com",
path:"/v15.0/1119744*************/messages",
method:"POST",
body:data,
headers: {
"Content-Type":"application/json",
Authorization:"Bearer EAAWNbICfuWEBAK5ObPbD******************************************************"
}
};
const req = https.request(options, res => {
res.on("data", d=> {
process.stdout.write(d);
});
});
req.on("error", error => {
console.error(error);
});
req.write(data);
req.end();
}
module.exports = {
SendMessageWhatsApp
};
Then I declare the routes for the get (to check token) and post (to receive and reply to messages) methods:
const expres = require("express");
const router = expres.Router();
const whatsappController = require("../controllers/whatsappControllers");
router
.get("/", whatsappController.VerifyToken)
.post("/", whatsappController.ReceivedMessage)
module.exports = router;
Last but not least the index file for the code to run correctly:
const express = require("express");
const apiRoute = require("./routes/routes");
const app = express();
const PORT = process.env.PORT || 3000
app.use(express.json());
app.use("/whatsapp", apiRoute);
app.listen(PORT, () => (console.log("El puerto es: " + PORT)));
I should clarify that I did the tests with Postman and they were all successful, it responds and receives messages correctly, finally I did the tests by uploading the bot to the Azure service and it works without problem until it has to answer/replicate the user's message.
The bot is not responding to the user when he talks to it but everything arrives correctly to the server and it processes it with a 200 response. I attach the evidence that there is no problem in the reception.
Finally I must say that in the meta platform I have everything configured as specified by the same platform, I have already configured the api to answer the messages through the webhooks and everything is correct, I just can't get the bot to answer correctly.
The bot is hosted in the Azure service.
Solved: some numbers have a problema with the api of WAB in my country (Argentina) the phone numbers start in +54 9 11. The problem is the 9 in the phone number, and this have a conflict in the servers of meta, Solution quit number 9 to numbers of this country and the message will send to user.

Strange 401 error in nodejs. when calling 2 API it gives auth error

When I am calling one API from the nodejs it is giving proper reply. But when I am adding one more call it is giving 401 error. I dont know if I have to close some parameter before calling another request.
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
var request = require('request')
var username = "shruti111";
var password = 'Welcome1';
var planId;
baseUrl = 'https://50d5a18993c046e585b90bc8cc5e1f80-jcs.oci.cloudonline.ml:443';
var baseUrlwoHttps = baseUrl.substring(8);
process.env["NO_PROXY"] = baseUrlwoHttps;
var getUrl = baseUrl + '/IMCMCSREST/rest/v1/PlannedCosts';
var options = {
url: getUrl,
auth: {
user: username,
password: password
}
}
request(options, function (err, res, body) {
if (err) {
console.dir(err)
return
}
var json = JSON.parse(body);
var arr = [];
for (i = 0; i < json.items.length; i++) {
if (json.items[i].PlanCode == 'Material Cost Planning - PO')
planId = json.items[i].PlanId;
//arr.push(json.items[i].PlanId, json.items[i].PlanCode);
}
console.log(planId);
})
Upto this point it is working properly. If I add below code in the same file it gives 401 error for both call. Otherwise it runs properly.
var getUrl = baseUrl + 'IMCMCSREST/rest/v1/PlannedCosts/' + planId + '/ child / CmiCpPlanCostTypesView';
var options = {
url: getUrl,
auth: {
user: username,
password: password
}
}
request(options, function (err, res, body) {
if (err) {
console.dir(err)
return
}
console.log(body);
var json = JSON.parse(body);
console.log(json);
var arr = [];
var x;
for (i = 0; i < json.items.length; i++) {
arr[i] = json.items[i].CostTypeId;
//arr.push(json.items[i].PlanId, json.items[i].PlanCode);
}
console.log(arr[i]);
})
I think the first problem here is the plandId variable you're using on second request does not have a value. What you can try is calling the second request on the callback of first request.
Another problem seems to be you are redefining existing variables, though its not fully clear as you didn't show the file as a whole.

Grant access to Common Data Service with adal-node

Cannot grant access to Common Data Service with NodeJS
I am implementing a simple Node function which will get some data from Common Data Service. I can get the accessToken already, but when I use this accessToken to access Common Data Service, the response is ‘Unauthorized’.
I followed the instruction here ( https://learn.microsoft.com/en-us/powerapps/developer/common-data-service/walkthrough-registering-configuring-simplespa-application-adal-js ) and is able to get it worked with simple page app.
I just want to port it to Node and have the app grant access to Common Data Service without requiring a user to login.
const fetch = require('node-fetch');
const AuthenticationContext = require('adal-node').AuthenticationContext;
module.exports = async function (context, req) {
context.log('JavaScript HTTP trigger function processed a request.');
const resource = "https://my-org.crm5.dynamics.com";
const clientId = 'my client id';
const clientSecret = 'my client secret';
const authorityHostUrl = 'https://login.microsoftonline.com';
const tenant = 'my-tenant-name.onmicrosoft.com'; // AAD Tenant name.
const authorityUrl = authorityHostUrl + '/' + tenant;
const authContext = new AuthenticationContext(authorityUrl);
const tokenResp = await new Promise((resolve, reject) => {
authContext.acquireTokenWithClientCredentials(resource, clientId, clientSecret, function (err, tokenResponse) {
if (err) {
context.error("cannot get token: " + err.stack);
return reject(err.stack);
} else {
return resolve(tokenResponse);
}
});
});
context.log("tokenResp: ", tokenResp); // The tokenResp contains accessToken
const cdsHeaders = {};
cdsHeaders["Authorization"] = "Bearer " + tokenResp.accessToken;
cdsHeaders["Accept"] = "application/json";
cdsHeaders["Content-Type"] = "application/json; charset=utf-8";
cdsHeaders["OData-MaxVersion"] = "4.0";
cdsHeaders["OData-Version"] = "4.0";
const endpointUrl = encodeURI(resource + "/api/data/v9.0/accounts?$select=name,address1_city&$top=10");
const dataResponse = await fetch(endpointUrl, { method: 'GET', headers: cdsHeaders });
console.log("response: ", dataResponse); // The dataResponse is 401 Unauthorized
context.res = { body: "Done" };
};
I got the solution: I have to 'Manually create a CDS for Apps application user' in order for it to work, regarding this document: https://learn.microsoft.com/en-us/powerapps/developer/common-data-service/authenticate-oauth#connect-as-an-app
Although the sample code is in C#, there are not too many differences between C# and Node.js clients.

Payment gateway for using with nodejs that accepts INR

I'm looking for a payment gateway that supports INR, that I can use with a nodejs app. Can't find anything that works. Any suggestions and pointers would be helpful.
I think citruspay is a good choice. The following links show integrations with node js.
Merchant Hosted Checkout (Citrus.js)
var crypto = require('crypto');
function generateSignature(merchantTxnId, request) {
//Need to change with your Secret Key
var secret_key = "***SEcRETKey***";
//Need to change with your Access Key
var accesskey = "***Access Key***";
//Should be unique for every transaction
var txn_id = merchantTxnId;
//Need to change with your Order Amount
var amount = "1.00";
var data = 'merchantAccessKey=' + accesskey + '&transactionId=' + txn_id + '&amount=' + amount;
// generate hmac
var hmac = crypto.createHmac('sha1', secret_key);
hmac.update(data);
return hmac.digest('hex');
}
Now include this signature in PaymentObject
Then listen for post on the Return URL
You have to generate signature and compare with the signature sent in the post data from citrus to make sure data is not tampered in the way.
var http = require("http");
var qs = require('querystring');
var crypto = require('crypto');
var secret_key = "MERCHANT_SECRET_KEY";
http.createServer(function(request, response) {
var body = "";
if (request.method = "POST") {
request.on("data", function(data) { body += data; });
request.on("end", function() {
var post = qs.parse(body);
var data_string = post['TxId'] + post['TxStatus'] + post['amount']
+ post['pgTxnNo'] + post['issuerRefNo'] + post['authIdCode']
+ post['firstName'] + post['lastName'] + post['pgRespCode']
+ post['addressZip'];
var signature = crypto.createHmac('sha1',
secret_key).update(data_string).digest('hex');
if (signature == post['signature']) {
response.writeHead(200, {"Content-Type": "application/json"});
console.log(post);
response.write(JSON.stringify(post));
}
else {
response.writeHead(403, {"Content-Type": "application/json"});
var error = {error : 'Transaction Failed', message: 'Signature Verification Failed'};
response.write(JSON.stringify(error));
}
response.end();
});
}
}).listen(3000);

Testing authenticated / unauthenticated download links with Nightwatch.js

I know there is a very similar question here: Testing download links with Nightwatch.js, but this case is slighly different.
I'm trying to test a download using nightwatch too, but it's not that simple due to the browser behavious which present a popup window to the user.
What i'm trying to do is to just download the file with node, and not using selenium (as in this python version: https://stackoverflow.com/a/24998532/447074)
So I made this custom assertion :
downloadFile.js :
var util = require('util');
var http = require('http');
var url = require('url');
exports.assertion = function(options, expected_status, msg) {
this.message = msg || util.format('Testing if authenticated file download works');
this.file_url = options.file_url;
this.cookie_content = options.cookie_content;
this.expected = expected_status;
this.pass = function(value) {
return this.expected == value
};
this.value = function(response) {
return response.statusCode;
};
this.command = function(callback) {
var options = url.parse(this.file_url);
options.headers = {
'Cookie': this.cookie_content
};
http.get(options, callback);
return true;
};
};
And using it in a test case like this:
// I would not use this here, but I added this for this post...
"Get cookie": function(browser) {
var settings = browser.globals;
var state = browser.globals.state;
browser
.getCookies(function(response) {
state.sessionid = "sessionid=" + response['value'].filter(function(cookie_value){
return cookie_value['name'] == 'sessionid';
})[0]['value'];
})
},
"An authenticated user can download the file": function(browser) {
var settings = browser.globals;
var state = browser.globals.state;
browser
.assert.downloadFile({file_url: "http://example.com/somefile.pdf", cookie_content: state.sessionid}, 200, "Testing Authenticated download");
},
"An unauthenticated user can not download the file": function(browser) {
var settings = browser.globals;
var state = browser.globals.state;
browser
.assert.downloadFile({file_url: "http://example.com/somefile.pdf", cookie_content: "sessionid= wrong-session;"}, 302, "Testing Unauthenticated dowload");
},
It's "almost" working, but somehow the test "stops" after the first test, just after the first downloadFile assertion.
Here is the result from the console
nightwatch -c ./nightwatch.json --test tests/documents/upload_test.js
[Upload Test] Test Suite
========================
Setting up...
Running: Get cookie
No assertions ran.
Running: An authenticated user can download the file
✔ Testing Authenticated dowload
Is it ok to make this kind of assertion in nightwatch?
Why does it stops the tests?
Thanks!
Ok I found the solution by reading the custom assertion doc again: http://nightwatchjs.org/guide#custom-assertions
The command function of the assertion needs to return this http://nightwatchjs.org/guide#custom-assertions.
Here is the downloadFile.js assertion :
var util = require('util');
var http = require('http');
var url = require('url');
exports.assertion = function(options, expected_status, msg) {
this.message = msg || util.format('Testing if authenticated file download works');
this.file_url = options.file_url;
this.cookie_content = options.cookie_content;
this.expected = expected_status;
this.pass = function(value) {
return this.expected == value
};
this.value = function(response) {
return response.statusCode;
};
this.command = function(callback) {
var options = url.parse(this.file_url);
options.headers = {
'Cookie': this.cookie_content
};
http.get(options, callback);
return this;
};
};

Resources