mamcx
mamcx

Reputation: 16186

Why does an actix-web service send text/plain instead of JSON for an auth error?

I made a webservice with actix and I'm trying to implement the auth flow like in Auth Web Microservice with Rust using Actix-Web 1.0 - Complete Tutorial:

use std::sync::Arc;
use std::sync::Mutex;

use actix_cors::Cors;
use actix_identity::{CookieIdentityPolicy, Identity, IdentityService};

use actix::prelude::*;
use actix::{Actor, SyncContext};
use actix_web::{
    dev::Payload, error, guard, http::header, middleware, web, App, Error as AWError, FromRequest,
    HttpRequest, HttpResponse, HttpServer, Responder, ResponseError,
};
use derive_more::Display;

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenResult {
    pub company: String,
    pub config: String,
}

#[derive(Debug, Display)]
pub enum ServiceError {
    #[display(fmt = "Internal Server Error")]
    InternalServerError(String),

    #[display(fmt = "BadRequest: {}", _0)]
    BadRequest(String),

    #[display(fmt = "Unauthorized")]
    Unauthorized,
}

impl ResponseError for ServiceError {
    fn error_response(&self) -> HttpResponse {
        match *self {
            ServiceError::InternalServerError(ref message) => {
                HttpResponse::InternalServerError().json(message)
            }
            ServiceError::BadRequest(ref message) => HttpResponse::BadRequest().json(message),
            ServiceError::Unauthorized => HttpResponse::Unauthorized().json("Unauthorized"), //This is executed
        }
    }
}

impl FromRequest for TokenResult {
    type Error = AWError;
    type Future = Result<TokenResult, Self::Error>;
    type Config = ();

    fn from_request(req: &HttpRequest, pl: &mut Payload) -> Self::Future {
        if let Some(identity) = Identity::from_request(req, pl)?.identity() {
            let user: TokenResult = serde_json::from_str(&identity)?;
            return Ok(user);
        }
        //This is executed
        Err(ServiceError::Unauthorized.into())
    }
}

fn client_config(token: TokenResult) -> HttpResponse {
    HttpResponse::Ok().json(token.config)
}

fn main() -> Result<(), AWError> {
    let sys = actix_rt::System::new("Api");

    let mut server = HttpServer::new(move || {
        App::new()
            .wrap(IdentityService::new(
                CookieIdentityPolicy::new(&[0; 32])
                    .name("auth-cookie")
                    .secure(false),
            ))
            .service(web::resource("/config").to(client_config))
    });

    server = server.bind("127.0.0.1:8080").unwrap();

    server.start();
    println!("Started http client: 127.0.0.1:8080");
    sys.run()?;
    Ok(())
}

When I test with httpie and others, I get text instead of JSON:

http -v  POST http://localhost:8080/config
POST /config HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 0
Host: localhost:8080
User-Agent: HTTPie/1.0.2


HTTP/1.1 401 Unauthorized
content-length: 12
content-type: text/plain
date: Tue, 10 Sep 2019 20:41:46 GMT

Unauthorized

All the other methods and errors except TokenResult return JSON.

My dependencies:

[dependencies]
actix = "0.8.3"
actix-files = "0.1.4"
actix-rt = "0.2.5"
actix-identity = "0.1.0"
actix-cors = "0.1.0"
actix-web = {version = "1.0.7", features=["flate2-rust"], default-features = false}
derive_more = "0.15.0"
futures = "0.1.29"
json = "0.12.0"
serde = { version = "1.0.100", features = ["derive"] }
serde_json = "1.0.40"
snafu = "0.5.0"

Upvotes: 3

Views: 1983

Answers (1)

mamcx
mamcx

Reputation: 16186

It is necessary to override render_response:

impl ResponseError for ServiceError {
    fn error_response(&self) -> HttpResponse {
        match *self {
            ServiceError::InternalServerError(ref message) => {
                HttpResponse::InternalServerError().json(message)
            }
            ServiceError::BadRequest(ref message) => HttpResponse::BadRequest().json(message),
            ServiceError::Unauthorized => HttpResponse::Unauthorized().json("Unauthorized"),
        }
    }

    fn render_response(&self) -> HttpResponse {
        self.error_response()
    }
}

Thanks to ddboline on Reddit for pointing to the solution.

Upvotes: 1

Related Questions