Always return Ok HttpResponse then do work in actix-web handler - asynchronous

I have a handler to initiate a password reset. It always returns a successful 200 status code, so that an attacker cannot use it to find out which email addresses are stored in the database. The problem is, if an email is in the database, it'll take a while for the request to be fulfilled (blocking user lookup and sending the actual email with a reset token). If the user is not in the db, the request returns very quickly, so an attacked would know the email is not there.
How would I go about returning the HTTP response right away while processing the request in the background?
pub async fn forgot_password_handler(
email_from_path: web::Path<String>,
pool: web::Data<Pool>,
redis_client: web::Data<redis::Client>,
) -> HttpResponse {
let conn: &PgConnection = &pool.get().unwrap();
let email_address = &email_from_path.into_inner();
// search for user with email address in users table
match users.filter(email.eq(email_address)).first::<User>(conn) {
Ok(user) => {
// some stuff omitted.. this is what happens:
// create random token for user and store a hash of it in redis (it'll expire after some time)
// send email with password reset link and token (not hashed) to client
// then return with
HttpResponse::Ok().finish(),
}
_ => HttpResponse::Ok().finish(),
}
}

You can use an Actix Arbiter to schedule an asynchronous task:
use actix::Arbiter;
async fn do_the_database_stuff(
email: String,
pool: web::Data<Pool>,
redis_client: web::Data<redis::Client>)
{
// async database code here
}
pub async fn forgot_password_handler(
email_from_path: web::Path<String>,
pool: web::Data<Pool>,
redis_client: web::Data<redis::Client>,
) -> HttpResponse {
let email = email_from_path.clone();
Arbiter::spawn(async {
do_the_database_stuff(
email,
pool,
redis_client
);
});
HttpResponse::Ok().finish()
}
If your database code is blocking, to prevent hogging the long-lived Actix worker threads, you could instead create a new Arbiter, with its own thread:
fn do_the_database_stuff(email: String) {
// blocking database code here
}
pub async fn forgot_password_handler(email_from_path: String) -> HttpResponse {
let email = email_from_path.clone();
Arbiter::new().exec_fn(move || {
async move {
do_the_database_stuff(email).await;
};
});
HttpResponse::Ok().finish()
}
This may be a bit more work because Pool and redis::Client are unlikely to be safe to share between threads, so you will have to solve that too. That's why I didn't include them in the example code.
It's better to use Arbiters than be tempted to spawn a new native thread with std::thread. If you mix the two, you can end up accidentally including code that messes up the worker. For example using std::thread::sleep in an async context would pause unrelated tasks that just happen to be scheduled on the same worker, and may not even have any effect on the task you intended.
Finally, you might also consider an architectural change. If you factor database-heavy tasks into their own microservices, you would solve this problem automatically. The web handler can then just send a message (Kafka, RabbitMQ, ZMQ, HTTP, or whatever you choose) and immediately return. This will let you scale the microservices independently of the webserver - 10x web server instances doesn't have to mean 10x database connections, if you only need one instance for the password reset service.

Related

In Firebase and Kotlin, in case of no network connection is there an easy way to handle endless network request looping?

In the case of network connectivity loss, the following code just loops endlessly and keeps making API calls. Is there a way to cancel with a timeout (for example, 5000 ms) using Firebase API? Or would I have to make my own Coroutine to handle this?
fun updateUserFieldInDB(
collectionPath: String,
strArr: ArrayList<String>,
onSuccess: (() -> Unit),
onFail: (() -> Unit)
) {
val fbUser = Firebase.auth.currentUser
if (fbUser == null) {
Log.i(TAG, "user is null....")
return
}
val db = Firebase.firestore
when (strArr.size) {
2 -> {
db.collection(collectionPath).document(fbUser.uid).update(strArr[0], strArr[1])
.addOnSuccessListener {
onSuccess()
}
.addOnFailureListener {
onFail()
}
}
}
}
The onSuccess ad onFail completion handlers for Firestore only fire once the write operation has been committed or rejected on the server. You should only use them if you're interested in detecting that situation, in which case the looping is to be expected.
If you only care whether the write operation was recorded by the Firestore client (in its local cache), the best way to detect that is when the update(strArr[0], strArr[1]) call completes.
So pretty much: when the next line of code executes, the write has been recorded locally; when the completion listeners fire, the write has been handled on the server.

Spawn reading data from multipart in actix-web

I tried the example of actix-multipart with actix-web v3.3.2 and actix-multipart v0.3.0.
For a minimal example,
use actix_multipart::Multipart;
use actix_web::{post, web, App, HttpResponse, HttpServer};
use futures::{StreamExt, TryStreamExt};
#[post("/")]
async fn save_file(mut payload: Multipart) -> HttpResponse {
while let Ok(Some(mut field)) = payload.try_next().await {
let content_type = field.content_disposition().unwrap();
let filename = content_type.get_filename().unwrap();
println!("filename = {}", filename);
while let Some(chunk) = field.next().await {
let data = chunk.unwrap();
println!("Read a chunk.");
}
println!("Done");
}
HttpResponse::Ok().finish()
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(save_file))
.bind("0.0.0.0:8080")?
.run()
.await
}
This works well, but I want to do with form data asynchronously. So I tried instead:
use actix_multipart::Multipart;
use actix_web::{post, web, App, HttpResponse, HttpServer};
use futures::{StreamExt, TryStreamExt};
#[post("/")]
async fn save_file(mut payload: Multipart) -> HttpResponse {
actix_web::rt::spawn(async move {
while let Ok(Some(mut field)) = payload.try_next().await {
let content_type = field.content_disposition().unwrap();
let filename = content_type.get_filename().unwrap();
println!("filename = {}", filename);
while let Some(chunk) = field.next().await {
let data = chunk.unwrap();
println!("Read a chunk.");
}
println!("Done");
}
});
HttpResponse::Ok().finish()
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(save_file))
.bind("0.0.0.0:8080")?
.run()
.await
}
(Added actix_web::rt::spawn to save_file.)
But this did not work -- the message "Done" never printed. The number of "Read a chunk" displayed in the second case was less than the first case, so I guess that field.next().await cannot terminate for some reason before completing reading all data.
I do not know so much about asynchronous programming, so I am not sure why field.next() did not work in actix_web::rt::spawn.
My question are: why is it, and how can I do with actix_web::rt::spawn?
When you make this call:
actix_web::rt::spawn(async move {
// do things...
});
spawn returns a JoinHandle which is used to poll the task. When you drop that handle (by not binding it to anything), the task is "detached", i.e., it runs in the background.
The actix documentation is not particularly helpful here, but actix uses the tokio runtime under the hood. A key issue is that in tokio, spawned tasks are not guaranteed to complete. The executor needs to know, somehow, that it should perform work on that future. In your second example, the spawned task is never .awaited, nor does it communicate with any other task via channels.
Most likely, the spawned task is never polled and does not make any progress. In order to ensure that it completes, you can either .await the JoinHandle (which will drive the task to completion) or .await some other Future that depends on work in the spawned task (usually by using a channel).
As for your more general goal, the work is already being performed asynchronously! Most likely, actix is doing roughly what you tried to do in your second example: upon receiving a request, it spawns a task to handle the request and polls it repeatedly (as well as the other active requests) until it completes, then sends a response.

How to make asynchronous calls from external services to actions on google?

I'm trying to connect Google Home to an external chatbot with actionssdk. I have an API that take user inputs and send them to my chatbot with webhook, but my chatbot make a response calling another endpoint of my API in an async way, and I can't show the response in actions on Google or Google Home.
I create an actionssdkApp.
const {
actionssdk,
SimpleResponse,
Image,
} = require('actions-on-google');
var app = actionssdk();
var express_app = express();
My API has 2 endpoints. One of them is for actions on google to send user inputs to my chatbot:
app.intent('actions.intent.MAIN', conv => {
console.log('entra en main');
conv.ask('Hi, how is it going?');
});
app.intent('actions.intent.TEXT', (conv, input) => {
var userId = conv.body.user.userId;
console.log(userId);
if(userId && input){
textFound(conv, input, userId);
}else{
textnotFound(conv);
}
});
TextFound function send user inputs to my chatbot with webhook, but the request doesn't receive the response. My chatbot call another endpoint with the text answer:
express_app.post('/webhook', bodyParser.json(), (req, res)=>{
console.log("Webhook");
const userId = req.body.userId;
if (!userId) {
return res.status(400).send('Missing User ID');
}
console.log(req.body);
res.sendStatus(200);
});
And here is where I want to send the answer to Google Home. But I need the conv object to show the answer in google Home, or actions on google, or any other device.
Edit:
My textFound function:
webhook.messageToBot(metadata.channelUrl, metadata.channelSecretKey, userId, input, function(err){
if(err){
console.log('Error in sending message');
conv.ask("Error in sending message");
}else{
conv.ask("some text");
}
});
From here my api send user inputs to my bot through messageToBot function:
request.post({
uri: channelUrl,
headers: headers,
body: body,
timeout: 60000,
followAllRedirects: true,
followOriginalHttpMethod: true,
callback: function(err, res, body) {
if (err) {
console.log('err: '+err);
callback(err);
} else {
console.log('Message sent');
callback(null);
}
}
});
From now on, my bot doesn't send a response but makes a call to /webhook endpoint of my api with the answer. But in this function I haven't de conv object and I can't send the answer to google. I don't know how to access to this object. Maybe there is an uri to connect with my project in actions on google from my api.
Typically, Actions on Google works in a request-response way. The user says something to the Action, and the Action replies with a response. That reply needs to come within about 5 seconds. If you think the call to /webhook can come that quickly, and you will only deliver a message to the user after they say something, you can have /webhook save the response in a queue for the user, and have your Intent handler be in a loop that checks this queue for any messages to reply with - if there is a message within 5 seconds, you reply with it, if not, you need to reply before the 5 seconds are up.
If you can't guarantee it will be done within 5 seconds, however, there are a couple of workarounds that might be useful depending on your needs.
The first is that you might be able to use notifications. In this scenario, you would send the message from the user and then close the conversation. When your /webhook endpiont is triggered, you would locate the user and send the notification to their Assistant. Unfortunately, this is a bit bulky, doesn't lead to a very interactive chat system, and notifications also aren't supported on smart speakers.
You can also look into using a Media Response to set up a way for you to poll for new messages periodically. Under this scheme, your user would send their message. In your reply to them, you would include a Media Response for some audio that plays for, say, 15 seconds. When the audio finishes, your Action will be called again and you can check to see if any messages have been queued up to be delivered to the user. If so, you relay those messages, followed by a Media Response gain. Otherwise, just send a Media Response. Your call to /webhook would have to put messages in a queue to be delivered to the user. This is more complex, especially to scale, but can be made more interactive. It is also a more general case of trying to handle it in a loop inside 5 seconds.

Realm: Notification after initial sync

According to the docs Realm can notify you when certain actions are taking place like "every time a write transaction is committed". I am using the Realm Object Server and the first time a user opens my app a large set of data is synched from the server down to the app. I would like to show a loading screen and not present the main UI of my app until Realm has completed its initial sync. Is there a way to be notified / determine when this process is complete?
The realm.io website just posted documentation on how to do this.
Asynchronously Opening Realms
If opening a Realm might require a time-consuming operation, such as applying migrations or downloading the remote contents of a synchronized Realm, you should use the openAsync API to perform all work needed to get the Realm to a usable state on a background thread before dispatching to the given queue. You should also use openAsync with Realms that are set read-only.
For example:
Realm.openAsync({
schema: [PersonSchema],
schemaVersion: 42,
migration: function(oldRealm, newRealm) {
// perform migration (see "Migrations" in docs)
}
}, (error, realm) => {
if (error) {
return;
}
// do things with the realm object returned by openAsync to the callback
console.log(realm);
})
The openAsync command takes a configuration object as its first parameter and a callback as its second; the callback function receives a boolean error flag and the opened Realm.
Initial Downloads
In some cases, you might not want to open a Realm until it has all remote data available. In such a case, use openAsync. When used with a synchronized Realm, this will download all of the Realm’s contents before the callback is invoked.
var carRealm;
Realm.openAsync({
schema: [CarSchema],
sync: {
user: user,
url: 'realm://object-server-url:9080/~/cars'
}
}, (error, realm) => {
if (error) {
return;
}
// Realm is now downloaded and ready for use
carRealm = realm;
});

Meteor http calls limitations

Currently, I use the built-in meteor http method (see http://docs.meteor.com/#http) for issuing http calls, on both my client and my server.
However, I'm experiencing two issues:
is it possible to cancel a request?
is it possible to have multiple query parameters which share the same key?
Are these just Meteor limitations, or are there ways to get both to work using Meteor?
I know I could you jquery on the clientside, and there must be a server-side solution which supports both as wel, but I'd prefer sticking with meteor code here.
"is it possible to cancel a request?"
HTTP.call() does not appear to return an object on which we could call something like a stop() method. Perhaps a solution would be to prevent execution of your callback based on a Session variable?
HTTP.call("GET", url, function(error, result) {
if (!Session.get("stopHTTP")) {
// Callback code here
}
});
Then when you reach a point where you want to cancel the request, do this:
Session.set("stopHTTP", true);
On the server, instead of Session perhaps you could use an environment variable?
Note that the HTTP.call() options object does accept a timeout key, so if you're just worried about the request never timing out, you can set this to whatever millisecond integer you want.
"is it possible to have multiple query parameters which share the same key?"
Yes, this appears to be possible. Here's a simple test I used:
Meteor code:
HTTP.call("GET", "http://localhost:1337", {
query: "id=foo&id=bar"
}, function(error, result) {
// ...
});
Separate Node.js server: (just the basic example on the Node.js homepage, with a console.log line to output the request URL with query string)
var http = require('http');
http.createServer(function(req, res) {
console.log(req.url); // Here I log the request URL, with the query string
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
When the Meteor server is run, the Node.js server logged:
/?id=foo&id=bar
Of course, this is only for GET URL query parameters. If you need to do this for POST params, perhaps you could store the separate values as a serialized array string with EJSON.stringify?

Resources