Parsing Tweets with akka-streams - twitter

Im using the following code to connect to Twitter and get Tweets, however Im not able to create JsValue. If only map with byteString.utf8String I can see the strings. But when I add framing i get an error:
Read 7925 bytes which is more than 1000 without seeing a line terminator
No matter how long i make the input im still getting this error. What do I need to change to get a stream of JsValue in my websocket?
Information about how one is expected to consume twitter streams can be found here
#Singleton
class TwitterController #Inject() (ws: WSClient, conf: Configuration) {
def tweets = WebSocket.accept[JsValue,JsValue] { implicit request =>
Flow.fromSinkAndSource(Sink.ignore, queryToSource("cat"))
}
def queryToSource(keyword: String): Source[JsValue, NotUsed] = {
val url = "https://stream.twitter.com/1.1/statuses/filter.json"
credentials.map { case (consumerKey, requestToken) =>
val request = ws.url(url)
.sign(OAuthCalculator(consumerKey, requestToken))
.withQueryString("track" -> keyword)
.withMethod("GET")
streamResponse(request)
.via(Framing.delimiter(ByteString("\r\n"), maximumFrameLength = 1000, allowTruncation = true))
.map { byteString =>
Json.parse(byteString.utf8String)
// byteString.utf8String
}
} getOrElse {
Source.single(Json.parse("Twitter credentials missing"))
}
}
private def streamResponse(request:WSRequest): Source[ByteString, NotUsed] = Source.fromFuture(request.stream()).flatMapConcat(_.body)
lazy val credentials: Option[(ConsumerKey, RequestToken)] = for {
apiKey <- conf.getString("twitter.apiKey")
apiSecret <- conf.getString("twitter.apiSecret")
token <- conf.getString("twitter.token")
tokenSecret <- conf.getString("twitter.tokenSecret")
} yield (
ConsumerKey(apiKey, apiSecret),
RequestToken(token, tokenSecret)
)
}

Related

Dart shelf - Middleware and handler execution order

I'm lost trying to understand how Dart shelf executes the middleware and handlers. From all the documentation I have read (and briefing it up) if you write a Middleware that returns null, then the execution goes down the pipeline.
Otherwise if the middleware returns a Response, then the execution down the pipeline is stopped, and the Response is returned to the caller.
I have a server with a simple pipeline like this:
var handler = const shelf.Pipeline()
.addMiddleware(shelf.logRequests())
//.addMiddleware(options)
.addMiddleware(auth)
.addHandler(Router.handle);
The auth middleware checks 3 cases: Register, Login and Verify.
Register -> Creates new user and returns Response.ok(token), or if nor possible Response.InternalServerError
Login -> Refreshes the token and returns Response.ok(token), or if not correct Response(401)
Verify -> Returns null when ok(should continue down the pipeline), or Response(403, forbidden)
The problem is, that I cannot stop the execution of the middlewares. If I make a successful login, still the program goes down the pipeline and calls the Router. Which of course doesn't have the path for register and returns 404 as it is expected to do.
According to shelf documentation, it is supposed to stop when a middleware returns a response. What the hell am I doing wrong?
This is the code of the auth Middleware for reference:
abstract class AuthProvider {
static JsonDecoder _decoder = const JsonDecoder();
static FutureOr<Response> handle(Request request) async {
print('Entering auth middleware');
if(request.url.toString() == 'login'){
print('into login from auth');
AuthProvider.auth(request);
}
else if(request.url.toString() == 'register'){
print('Into register from auth');
RegisterController.handle(request);
}
else {
print('Into verify from auth');
AuthProvider.verify(request);
}
}
static FutureOr<Response> auth(Request request) async {
print('Entering auth');
String sql;
var query = ExecQuery();
try {
dynamic data = jsonDecode(await request.readAsString()) as Map<String, dynamic>;
final user = data['email'].toString();
final hash = Hash.create(data['password'].toString());
sql =
'''SELECT COUNT(*) FROM public.user WHERE (email = '${user}' AND password = '${hash}')''';
await query.countSql(sql);
if (query.result.status && query.result.opResult[0][0] == 1) {
JwtClaim claim = JwtClaim(
subject: user,
issuer: 'Me',
audience: ['users'],
);
final token = issueJwtHS256(claim, config.secret);
sql = '''UPDATE public.user SET token = '${token}'
WHERE (email = '${user}' AND password = '${hash}')''';
await query.rawQuery(sql);
return Response.ok(token);
}
else{throw Exception();}
} catch (e) {
return Response(401, body: 'Incorrect username/password');
}
}
static FutureOr<Response> verify(Request request) async {
print('Entering verify');
try {
final token = request.headers['Authorization'].replaceAll('Bearer ', '');
print('Received token: ${token}');
final claim = verifyJwtHS256Signature(token, config.secret);
print('got the claim');
claim.validate(issuer: 'ACME Widgets Corp',
audience: 'homacenter');
print ('returning null in middleware');
return null;
} catch(e) {
print(e.toString());
return Response.forbidden('Authorization rejected');
}
}
}
I reply myself... after losing days in this, a return was missing, that made the pipeline keep going. Issue closed.
abstract class AuthProvider {
static JsonDecoder _decoder = const JsonDecoder();
static FutureOr<Response> handle(Request request) async {
if(request.url.toString() == 'login'){
return AuthProvider.auth(request);
}
else if(request.url.toString() == 'register'){
return RegisterController.handle(request);
}
else {
return AuthProvider.verify(request);
}
}

Postman GET request to Binance API

I'm trying to send a GET request to Binance's API, but I don't exactly know how to.
Here is the documentation page: https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#account-information-user_data
I have a private apiKey and secretKey.
I can do a general request to Binance, but I cannot get my private data, using my private keys.
First try:
For the GET request in Postman I use this string:
https://api.binance.com/api/v3/account?timestamp=1499827319559&signature=here_I_put_my_secret_key
And I pass as a header as Danny suggested the apiKey.
But I get:
{
"code": -1021,
"msg": "Timestamp for this request is outside of the recvWindow."
}
Thanks.
I solved this correcting the time using javascript in Postman.
Another easy fix is to use the ccxt library : https://github.com/ccxt/ccxt
This might be what you're after, as per the documentation.
https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#endpoint-security-type
API-keys are passed into the Rest API via the X-MBX-APIKEY header.
In your Request, add that as the header key and your API Key as the value.
You can try this. This is working for me. Just Replace your API_KEY and SECRET
You need to retrieve serverTime time from https://api.binance.com/api/v3/time and need to use that serverTime to sign the request.
GET : https://api.binance.com/api/v3/account?timestamp={{timestamp}}&signature={{signature}}
Header:
Content-Type:application/json
X-MBX-APIKEY:YOUR_API_KEY
Pre-request Script :
pm.sendRequest('https://api.binance.com/api/v3/time', function (err, res) {
console.log('Timestamp Response: '+res.json().serverTime);
pm.expect(err).to.not.be.ok;
var timestamp = res.json().serverTime;
postman.setEnvironmentVariable('timestamp',timestamp)
postman.setGlobalVariable('timestamp',timestamp)
let paramsObject = {};
const binance_api_secret = 'YOUR_API_SECRET';
const parameters = pm.request.url.query;
parameters.map((param) => {
if (param.key != 'signature' &&
param.key != 'timestamp' &&
!is_empty(param.value) &&
!is_disabled(param.disabled)) {
paramsObject[param.key] = param.value;
}
})
Object.assign(paramsObject, {'timestamp': timestamp});
if (binance_api_secret) {
const queryString = Object.keys(paramsObject).map((key) => {
return `${encodeURIComponent(key)}=${paramsObject[key]}`;
}).join('&');
console.log(queryString);
const signature = CryptoJS.HmacSHA256(queryString, binance_api_secret).toString();
pm.environment.set("signature", signature);
}
function is_disabled(str) {
return str == true;
}
function is_empty(str) {
if (typeof str == 'undefined' ||
!str ||
str.length === 0 ||
str === "" ||
!/[^\s]/.test(str) ||
/^\s*$/.test(str) ||
str.replace(/\s/g,"") === "")
{
return true;
}
else
{
return false;
}
}
}
);
Get the official Postman collections for the Binance API from here:
https://github.com/binance/binance-api-postman
Import the desired collection and environment in Postman, for instance
binance_spot_api_v1.postman_collection.json
and binance_com_spot_api.postman_environment.json
Add your API key to the binance-api-key environment variable and your secret key to the binance-api-secret variable.
CAUTION: Limit what the key can do in Binance key management. Do not use this key for production, only for testing. Create new key for production.
For the signed requests calculate the signature in a Pre-request Script then set the signature environment variable.
Example Pre-request Script:
function resolveQueryString() {
const query = JSON.parse(JSON.stringify(pm.request.url.query))
const keyPairs = []
for (param of query) {
if (param.key === 'signature') continue
if (param.disabled) continue
if (param.value === null) continue
const value = param.value.includes('{{') ? pm.environment.get(param.key) : param.value
keyPairs.push(`${param.key}=${value}`)
}
return keyPairs.join('&')
}
const signature = CryptoJS.HmacSHA256(
resolveQueryString(),
pm.environment.get('binance-api-secret')
).toString(CryptoJS.enc.Hex)
pm.environment.set('signature', signature)

unable to understand why validateOpt will return `None` instead of `JsError`

In the following code, I am unable to understand why validateOpt might return value JsSuccess(None) instead of JsError
def getQuestion = silhouette.UserAwareAction.async{
implicit request => {
val body: AnyContent = request.body
val jsonBodyOption: Option[JsValue] = body.asJson
jsonBodyOption.map((jsonBody:JsValue) => { //body is json
val personJsonJsResultOption = jsonBody.validateOpt[Person]//check that json structure is correct
personJsonJsResultOption match {
case personSuccessOption: JsSuccess[Option[Person]] => { //json is correct
val personOption = personSuccessOption.getOrElse(None) //why would getOrElse return None??
personOption match {
case Some(person) => {
... }
case None =>{ //I am not sure when this will be triggered.
...
}
}
}
}
case e: JsError => {
...
}
}
}
})
.getOrElse(//body is not json
...)
}
}
validateOpt by design considers success to be not only when body provides actual Person but also when Person is null or not provided. Note how documentation explains why JsSuccess(None) is returned:
/**
* If this result contains `JsNull` or is undefined, returns `JsSuccess(None)`.
* Otherwise returns the result of validating as an `A` and wrapping the result in a `Some`.
*/
def validateOpt[A](implicit rds: Reads[A]): JsResult[Option[A]]
Seems like your requirement is that Person must always be provided to be considered successful, so in this case validate should be used instead of validateOpt.

Use MSAL in Microsoft Teams not work JS

I work on an MS Teams app with a connection to MS Graph.
I'm trying to get an access token for MS Graph in MS Teams. To get a token I'm using MSAL js.
If I run the App with gulp serve I receive a valid token and I have access to the MS Graph endpoints. But if I build the app and install it in MS Teams the function userAgentApplication.acquireTokenSilent(config) is never executed. I tested it with a console.log before and after the call. There is no error thrown.
Do you have any idea why the above snippet is not executed in MS Teams (app and webapp)?
NEW:
On Home:
export function login() {
const url = window.location.origin + '/login.html';
microsoftTeams.authentication.authenticate({
url: url,
width: 600,
height: 535,
successCallback: function(result: string) {
console.log('Login succeeded: ' + result);
let data = localStorage.getItem(result) || '';
localStorage.removeItem(result);
let tokenResult = JSON.parse(data);
storeToken(tokenResult.accessToken)
},
failureCallback: function(reason) {
console.log('Login failed: ' + reason);
}
});
}
On Login
microsoftTeams.initialize();
// Get the tab context, and use the information to navigate to Azure AD login page
microsoftTeams.getContext(function (context) {
// Generate random state string and store it, so we can verify it in the callback
let state = _guid();
localStorage.setItem("simple.state", state);
localStorage.removeItem("simple.error");
// See https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols-implicit
// for documentation on these query parameters
let queryParams = {
client_id: "XXX",
response_type: "id_token token",
response_mode: "fragment",
scope: "https://graph.microsoft.com/User.Read openid",
redirect_uri: window.location.origin + "/login-end.html",
nonce: _guid(),
state: state,
login_hint: context.loginHint,
};
// Go to the AzureAD authorization endpoint (tenant-specific endpoint, not "common")
// For guest users, we want an access token for the tenant we are currently in, not the home tenant of the guest.
let authorizeEndpoint = `https://login.microsoftonline.com/${context.tid}/oauth2/v2.0/authorize?` + toQueryString(queryParams);
window.location.assign(authorizeEndpoint);
});
// Build query string from map of query parameter
function toQueryString(queryParams) {
let encodedQueryParams = [];
for (let key in queryParams) {
encodedQueryParams.push(key + "=" + encodeURIComponent(queryParams[key]));
}
return encodedQueryParams.join("&");
}
// Converts decimal to hex equivalent
// (From ADAL.js: https://github.com/AzureAD/azure-activedirectory-library-for-js/blob/dev/lib/adal.js)
function _decimalToHex(number) {
var hex = number.toString(16);
while (hex.length < 2) {
hex = '0' + hex;
}
return hex;
}
// Generates RFC4122 version 4 guid (128 bits)
// (From ADAL.js: https://github.com/AzureAD/azure-activedirectory-library-for-js/blob/dev/lib/adal.js)
function _guid() {...}
on login end
microsoftTeams.initialize();
localStorage.removeItem("simple.error");
let hashParams = getHashParameters();
if (hashParams["error"]) {
// Authentication/authorization failed
localStorage.setItem("simple.error", JSON.stringify(hashParams));
microsoftTeams.authentication.notifyFailure(hashParams["error"]);
} else if (hashParams["access_token"]) {
// Get the stored state parameter and compare with incoming state
let expectedState = localStorage.getItem("simple.state");
if (expectedState !== hashParams["state"]) {
// State does not match, report error
localStorage.setItem("simple.error", JSON.stringify(hashParams));
microsoftTeams.authentication.notifyFailure("StateDoesNotMatch");
} else {
// Success -- return token information to the parent page.
// Use localStorage to avoid passing the token via notifySuccess; instead we send the item key.
let key = "simple.result";
localStorage.setItem(key, JSON.stringify({
idToken: hashParams["id_token"],
accessToken: hashParams["access_token"],
tokenType: hashParams["token_type"],
expiresIn: hashParams["expires_in"]
}));
microsoftTeams.authentication.notifySuccess(key);
}
} else {
// Unexpected condition: hash does not contain error or access_token parameter
localStorage.setItem("simple.error", JSON.stringify(hashParams));
microsoftTeams.authentication.notifyFailure("UnexpectedFailure");
}
// Parse hash parameters into key-value pairs
function getHashParameters() {
let hashParams = {};
location.hash.substr(1).split("&").forEach(function (item) {
let s = item.split("="),
k = s[0],
v = s[1] && decodeURIComponent(s[1]);
hashParams[k] = v;
});
return hashParams;
}
Even though my answer is quite late, but I faced the same problem recently.
The solution is farely simple: MSALs silent login does not work in MSTeams (yet) as MSTeams relies on an IFrame Approach that is not supported by MSAL.
You can read all about it in this Github Issue
Fortunately, they are about to release a fix for this in Version msal 1.2.0 and there is already an npm-installable beta which should make this work:
npm install msal#1.2.0-beta.2
Update: I tried this myself - and the beta does not work as well. This was confirmed by Microsoft in my own Github Issue.
So I guess at the time being, you simply can't use MSAL for MS Teams.

initializing private variable for subsequent usage

Can you initialize a private value in a module, and then later call another function to read the value? I'm getting an empty string though.
data/Credentials.fs
type Credentials = {
mutable clientId: string;
}
Authentication.fs
module Authentication =
let private credentials = {
clientId = "old";
}
let init (claims: Credentials) =
credentials.clientId <- claims.clientId // updating value
let requestToken =
printfn "reading %s\n" credentials.clientId // reading updated value
AuthenticationTest.fs
let credentials = {
clientId = "new";
}
init credentials // set credentials
requestToken // read credentials
Expected output:
reading new
Actual Output:
reading old
requestToken is defined as variable, which contains result of printfn "reading %s\n" credentials.clientId. What you want is probably
let requestToken () =
printfn "reading %s\n" credentials.clientId
...
requestToken ()

Resources