Slack WebAPI fails with not_authed - slack

I'm attempting to post interactive messages to slack as a Bot User (using chat.postMessage, etc).
Although I am passing in the Bot Access Token (as received from the initial OAuth) I keep getting an error response stating "not_authed".
I get the same when I attempt auth.test.
I'm doing something like the following with "request" in node.js:
app.get("/testAuth/test", function(req,res){
console.log("in testAuth/test...sending test message to Slack");
var bToken = process.env.TESTBOT_ACCESS_TOKEN;
var slackMessageURL = "https://slack.com/api/auth.test";
var postOptions = {
uri: slackMessageURL,
method: "POST",
token: bToken
};
request(postOptions, (error, response, body) => {
if(error){
console.log("OOPPPPS....we hit an error in auth.test: " + error);
} else {
console.log("auth.test response: " + JSON.stringify(response));
}
});
res.send("Sent Test...check logs");
});
which results with:
auth.test response: {"statusCode":200,"body":"{\"ok\":false,\"error\":\"not_authed\"}",...
According to the Slack WebAPI docs, if I'm posting as the Bot, I should use the Bot's access token (as received from the initial oauth), but figure I'm either formatting my request incorrectly, or the token is not what Slack is expecting.

Ok, after talking with Slack support, it appears (at least) the WebAPIs I am calling don't yet support application/json. These do work with x-www-form-urlencoded.
Looking at this post
I was able to cobble together the following which auth'd successfully:
//up top
var request = require("request");
var querystring = require("querystring");
//...
app.get("/testAuth/test", function(req,res){
console.log("in testAuth/test...sending test message to Slack");
var bToken = process.env.TESTBOT_ACCESS_TOKEN;
var message = {
token: bToken
};
var messageString = querystring.stringify(message);
var messageLength = messageString.length;
var slackMessageURL = "https://slack.com/api/auth.test";
var postOptions = {
headers: {
"Content-length": messageLength,
"Content-type": "application/x-www-form-urlencoded"
},
uri: slackMessageURL,
body: messageString,
method: "POST"
};
request(postOptions, (error, response, body) => {
if(error){
console.log("OOPPPPS....we hit an error in auth.test: " + error);
} else {
console.log("auth.test response: " + JSON.stringify(response));
}
});
res.send("Sent Test...check logs");
});

Related

How should a botframework webchat conversation be maintained for over an hour?

I have looked through the documentation for botframework-webchat and have not been able to find any documentation on how conversations over 1 hour should be handled properly. This situation is most likely to occur if a web page is left idle in the background for an extended period of time.
The directline connection is maintained as long as the webchat remains active on a web page. The problem occurs after a page refresh.
The initial short term solution is to store the relevant conversation information in session storage, such as a token. The problem is that the token for the conversation is refreshed every 15 minutes. The refreshed token must be retrieved in order to maintain the conversation upon a page refresh.
I am sure a hacky work around exists for retrieving the refreshed token from the directline client object using an event callback.
Ideally, I am looking for a clean framework designed approach for handling this situation.
Though a working solution is better than no solution.
Relevant Link:
https://github.com/microsoft/BotFramework-WebChat
Thanks.
You can achieve this by implementing cookies in your client side. you can set cookies expiration time to 60 min and you can use watermark to make your chat persistent for one hour.
Passing cookie to and from Bot Service.
You can achieve this by setting up a "token" server. In the example below, I run this locally when I am developing/testing my bot.
You can use whatever package you want, however I landed on "restify" because I include it in the index.js file of my bot. I simply create a new server, separate from the bot's server, and assign it a port of it's own. Then, when I run the bot it runs automatically, as well. Put your appIds, appPasswords, and secrets in a .env file.
Then, in your web page that's hosting your bot, simply call the endpoint to fetch a token. You'll also notice that the code checks if a token already exists. If so, then it set's an interval with a timer for refreshing the token. The interval, at 1500000 ms, is set to run before the token would otherwise expire (1800000 ms). As such, the token is always getting refreshed. (Just popped in my head: may be smart to log the time remaining and the amount of time that passed, if the user navigated away, in order to set the interval to an accurate number so it refreshes when it should. Otherwise, the interval will reset with the expiration time being something much less.)
Also, I included some commented out code. This is if you want your conversations to persist beyond page refreshes or the user navigating away and returning. This way current conversations aren't lost and the token remains live. May not be necessary depending on your needs, but works well with the above.
Hope of help!
Token Server
/**
* Creates token server
*/
const path = require('path');
const restify = require('restify');
const request = require('request');
const bodyParser = require('body-parser');
const ENV_FILE = path.join(__dirname, '.env');
require('dotenv').config({ path: ENV_FILE });
const corsToken = corsMiddleware({
origins: [ '*' ]
});
// Create HTTP server.
let server = restify.createServer();
server.pre(cors.preflight);
server.use(cors.actual);
server.use(bodyParser.json({
extended: false
}));
server.listen(process.env.port || process.env.PORT || 3500, function() {
console.log(`\n${ server.name } listening to ${ server.url }.`);
});
// Listen for incoming requests.
server.post('/directline/token', (req, res) => {
// userId must start with `dl_`
const userId = (req.body && req.body.id) ? req.body.id : `dl_${ Date.now() + Math.random().toString(36) }`;
const options = {
method: 'POST',
uri: 'https://directline.botframework.com/v3/directline/tokens/generate',
headers: {
'Authorization': `Bearer ${ process.env.directLineSecret }`
},
json: {
user: {
ID: userId
}
}
};
request.post(options, (error, response, body) => {
// response.statusCode = 400;
if (!error && response.statusCode < 300) {
res.send(body);
console.log('Someone requested a token...');
} else if (response.statusCode === 400) {
res.send(400);
} else {
res.status(500);
res.send('Call to retrieve token from DirectLine failed');
}
});
});
// Listen for incoming requests.
server.post('/directline/refresh', (req, res) => {
// userId must start with `dl_`
const userId = (req.body && req.body.id) ? req.body.id : `dl_${ Date.now() + Math.random().toString(36) }`;
const options = {
method: 'POST',
uri: 'https://directline.botframework.com/v3/directline/tokens/refresh',
headers: {
'Authorization': `Bearer ${ req.body.token }`,
'Content-Type': 'application/json'
},
json: {
user: {
ID: userId
}
}
};
request.post(options, (error, response, body) => {
if (!error && response.statusCode < 300) {
res.send(body);
console.log('Someone refreshed a token...');
} else {
res.status(500);
res.send('Call to retrieve token from DirectLine failed');
}
});
});
webchat.html
<script>
(async function () {
let { token, conversationId } = sessionStorage;
[...]
if ( !token || errorCode === "TokenExpired" ) {
let res = await fetch( 'http://localhost:3500/directline/token', { method: 'POST' } );
const { token: directLineToken, conversationId, error } = await res.json();
// sessionStorage[ 'token' ] = directLineToken;
// sessionStorage[ 'conversationId' ] = conversationId;
token = directLineToken;
}
if (token) {
await setInterval(async () => {
let res = await fetch( 'http://localhost:3500/directline/refresh', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify( { token: token } )
} );
const { token: directLineToken, conversationId } = await res.json();
// sessionStorage[ 'token' ] = directLineToken;
// sessionStorage[ 'conversationId' ] = conversationId;
token = directLineToken;
}, 1500000)
}
// if ( conversationId ) {
// let res = await fetch( `https://webchat.botframework.com/v3/directline/conversations/${ conversationId }`, {
// method: 'GET',
// headers: {
// 'Authorization': `Bearer ${ token }`,
// 'Content-Type': 'application/json'
// },
// } );
// const { conversationId: conversation_Id, error } = await res.json();
// if(error) {
// console.log(error.code)
// errorCode = error.code;
// }
// conversationId = conversation_Id;
// }
[...]
window.ReactDOM.render(
<ReactWebChat
directLine={ window.WebChat.createDirectLine({ token });
/>
),
document.getElementById( 'webchat' );
});
</script>
The solution involved storing the conversation id in session storage instead of the token. Upon a page refresh a new token will be retrieved.
https://github.com/microsoft/BotFramework-WebChat/issues/2899
https://github.com/microsoft/BotFramework-WebChat/issues/2396#issuecomment-530931579
This solution works but it is not optimal. A better solution would be to retrieve the active token in the directline object and store it in session storage. The problem is that a way to cleanly way to retrieve a refreshed token from a directline object does not exist at this point.

Using slack webhook with node

I am trying to use slack webhook. I can read a lot of variation about how I should proceed, but until now, none of them worked properly.
I am using the request node module to make the api call, but I can change if needed.
First try following this
import request from 'request';
const url = 'https://hooks.slack.com/services/xxx';
const text = '(test)!';
request.post(
{
headers : { 'Content-type' : 'application/json' },
url,
payload : JSON.stringify({ text }),
},
(error, res, body) => console.log(error, body, res.statusCode)
);
I get : null 400 'invalid_payload'
Next try following this
request.post(
{
headers : { 'Content-type' : 'application/json' },
url,
form : JSON.stringify({ text }),
},
(error, res, body) => console.log(error, body, res.statusCode)
);
This time, it works, but Slack displays: %28test%29%21 instead of (test)!
Did I miss something?
Based on your second example and the working Postman request this is how I got it to work, forgive my change to require as I am running older node version right now. I am not exactly sure what your data would look like that you want to post to Slack, that may change how you want to assemble this.
const request = require('request');
const url = 'https://hooks.slack.com/services/xxxxx';
const text = '(test)!';
request.post(
{
headers : { 'Content-type' : 'application/json' },
url,
form : {payload: JSON.stringify({ text } )}
},
(error, res, body) => console.log(error, body, res.statusCode)
);
If you want to use request you may want to check how slack-node is posting the data, here the relevant snipped from slack-node
Slack.prototype.webhook = function(options, callback) {
var emoji, payload;
emoji = this.detectEmoji(options.icon_emoji);
payload = {
response_type: options.response_type || 'ephemeral',
channel: options.channel,
text: options.text,
username: options.username,
attachments: options.attachments,
link_names: options.link_names || 0
};
payload[emoji.key] = emoji.val;
return request({
method: "POST",
url: this.webhookUrl,
body: JSON.stringify(payload),
timeout: this.timeout,
maxAttempts: this.maxAttempts,
retryDelay: 0
}, function(err, body, response) {
if (err != null) {
return callback(err);
}
return callback(null, {
status: err || response !== "ok" ? "fail" : "ok",
statusCode: body.statusCode,
headers: body.headers,
response: response
});
});
};
You can try the slack-node module, wraps the post to the hook. Here a reduced modified real world example I used to push notifications for AWS instances.
[EDIT] Changed to use your text
Now, using slack-node, you assemble the {} yourself, adding text: and other parameters yourself and pass it to .webhook
const Slack = require('slack-node');
const webhookUri = 'https://hooks.slack.com/services/xxxxx';
const slack = new Slack();
slack.setWebhook(webhookUri);
text = "(test)!"
slack.webhook({
text: text
// text: JSON.stringify({ text })
}, function(err, response) {
console.log(err, response);
});
I finally went with Slack-webhook that I liked better than slack-node. The solution of d parolin is the best answer to my question, but I wanted to mention the work of pthm for completion.

react-native fetch with authorization header sometime return 401

I'm facing some issue whereby I sometime will get status code 401 (Unauthorised) from my phone. I'm trying to access to an API from my computer localhost (192.168.0.7).
I've a screen, when I click on a button it will navigate to a page and it will request data through API. And when I go back and navigate to same page again, it sometime will return me code 401.
So if I repeat the same step (navigate and go back) let's say 10 times. I'm getting Unauthorised like 5-7 times.
Below are my code
export function getMyCarpool(param,token) {
return dispatch => {
var requestUrl = _api + 'GetMyProduct?' + param;
fetch(requestUrl, {
method: "get",
headers: new Headers({
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer ' + token
})
})
.then((request) => {
console.log(request);
if(request.status == 200)
return request.json();
else if(request.status == 401) {
//dispatch(logout());
throw new Error('Unauthorized access.');
}
else
throw new Error('Failed to request, please try again.');
})
.then((response) => {
var message = response.message;
if(response.success == 'true')
dispatch({ message, type: GET_MY_PRODUCT_SUCCESS });
else
dispatch({ message, type: GET_MY_PRODUCT_FAILED });
})
.catch(error => {
var message = error.message;
dispatch({ message, type: GET_MY_PRODUCT_FAILED });
});
}
I've check the token in my phone and also trying to make many request using postman. So I don't think it's server side problem.
I'm using Laravel and using laravel passport for API authentication. I not sure why this happen if I continue to access many time, any help is greatly appreciated.
UPDATE :: I'm trying to capture whether the http request has the token from this link, and I don't get the problem anymore.
It's a healthy mechanism for token expire. Maybe you have your token (access_token) for 5 minutes, then the token expired, you should use refresh_token to regain another new token (access_token).
For code explanation:
async function fetchService(url) {
const reqSetting = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${Auth.access_token}`,
},
};
const prevRequest = { url, reqSetting };
const resp = await fetch(url, reqSetting);
if (!resp.ok) {
const error = new Error(resp.statusText || 'Request Failed!');
if (resp.status === 401 || resp.status === 400) {
const responseClone = resp.clone();
const errorInfo = await resp.json();
if (errorInfo.error == 'invalid_token') {
// console.log('Token Expired', errorInfo);
try {
await refreshToken();
const response = await fetchService(prevRequest.url);
return response;
} catch (err) {
// handle why not refresh a new token
}
}
return responseClone;
}
error.errorUrl = url;
error.code = resp.status;
throw error;
}
return resp;
}
Where the refresh token function is :
async function refreshToken() {
const url = 'https://example.com/oauth/token';
const data = {
grant_type: 'refresh_token',
refresh_token: Auth.refresh_token,
};
try {
const res = await fetch(url, data);
const data = res.json();
Auth.access_token = data.access_token;
Auth.refresh_token = data.refresh_token;
return true;
} catch (error) {
throw error;
}
}
This fetchService will automatic regain a new token if old expired and then handle old request.
PS.
If you have multiple requests same time, the fetchService will need a little optimization. You'd better choose another regain token strategy like saga.

BeagleBone Black sending data to a web server using BoneScript

How do I send data (via ajax post or get) from BeagleBone Black to a web server using BoneScript?
As I understand, XMLHttpRequest does not exist. Is there another approach?
I found the solution: In fact, I see this operation is not related to beaglebone. It relates to nodejs. So, the key is require('http') by node.
var http = require('http');
http.get('http://www.example.com', function(response) {
var response_data = '';
response.on('data', function(chunk) {
response_data += chunk;
})
.on('end', function() {
console.log(response_data);
})
.on('error', function(e) {
console.log('Error: ' + e.message);
});
});
OR if you want to do that simpler, you can use Requistify
npm install requestify
After installed you can use it like:
var requestify = require('requestify');
// GET Example
requestify.get('http://example.com').then(function(response) {
// Get the response body (JSON parsed - JSON response or jQuery object in case of XML response)
response.getBody();
// Get the response raw body
response.body;
});
// POST example
requestify.post('http://example.com', {
hello: 'world'
})
.then(function(response) {
// Get the response body
response.getBody();
});

Firefox Addon Request Module - Authentication

I am using firefox addons sdk.
I have created a plugin and I wanted to send request out of it. I came across following code on Mozilla developer site.
var Request = require("sdk/request").Request;
var latestTweetRequest = Request({
url: "https://api.twitter.com/1/statuses/user_timeline.json?screen_name=mozhacks&count=1",
onComplete: function (response) {
var tweet = response.json[0];
console.log("User: " + tweet.user.screen_name);
console.log("Tweet: " + tweet.text);
}
});
// Be a good consumer and check for rate limiting before doing more.
Request({
url: "http://api.twitter.com/1/account/rate_limit_status.json",
onComplete: function (response) {
if (response.json.remaining_hits) {
latestTweetRequest.get();
} else {
console.log("You have been rate limited!");
}
}
}).get();
Here I cannot get any option to pass credentials along with request. As long as it is possible I want to avoid passing credentials along with url e.g. http://username:password#example.com, because many time special characters in password creates issue. So how to pass credentials with this request.
var { encode, decode } = require("sdk/base64");
// use encode() to base64 encode your credentials
var encodedCredentials = encode(email + ':' + password);
var testRequest = FRequest({
url: url,
headers: {
'Authorization': 'Basic ' + encodedCredentials
},
onComplete: function(response){
addOnPanel.port.emit('event', response.json)
}
}).get();

Resources