Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runtimes/core+js: hide error internal message in responses #1518

Merged
merged 5 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 23 additions & 18 deletions runtimes/core/src/api/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ use crate::names::EndpointName;
use crate::trace;
use crate::{model, Hosted};

use super::reqauth::caller::Caller;

#[derive(Debug)]
pub struct SuccessResponse {
pub status: axum::http::StatusCode,
Expand Down Expand Up @@ -61,30 +63,31 @@ impl IntoResponse for SuccessResponse {
Ok(()) => bld
.body(axum::body::Body::from(buf.into_inner().freeze()))
.unwrap(),
Err(err) => Error::internal(err).into_response(),
Err(err) => Error::internal(err).to_response(None),
}
}
None => bld.body(axum::body::Body::empty()).unwrap(),
}
}
}

impl AsRef<Error> for Error {
fn as_ref(&self) -> &Self {
self
}
pub trait ToResponse {
fn to_response(&self, caller: Option<Caller>) -> axum::response::Response;
}

impl IntoResponse for Error {
fn into_response(self) -> axum::http::Response<axum::body::Body> {
self.as_ref().into_response()
}
}
impl ToResponse for Error {
fn to_response(&self, caller: Option<Caller>) -> axum::http::Response<axum::body::Body> {
// considure response to be external if caller is gateway, or if the caller is
// unknown
let internal_call = caller.map(|caller| !caller.is_gateway()).unwrap_or(false);

impl IntoResponse for &Error {
fn into_response(self) -> axum::http::Response<axum::body::Body> {
let mut buf = BytesMut::with_capacity(128).writer();
serde_json::to_writer(&mut buf, &self).unwrap();

if internal_call {
serde_json::to_writer(&mut buf, &self).unwrap();
} else {
serde_json::to_writer(&mut buf, &self.as_external()).unwrap();
}

axum::http::Response::builder()
.status::<axum::http::status::StatusCode>(self.code.into())
Expand Down Expand Up @@ -498,9 +501,11 @@ impl EndpointHandler {
Box::pin(async move {
let request = match self.parse_request(axum_req).await {
Ok(req) => req,
Err(err) => return err.into_response(),
Err(err) => return err.to_response(None),
};

let internal_caller = request.internal_caller.clone();

// If the endpoint isn't exposed, return a 404.
if !self.endpoint.exposed && !request.allows_private_endpoint_call() {
return Error {
Expand All @@ -510,7 +515,7 @@ impl EndpointHandler {
stack: None,
details: None,
}
.into_response();
.to_response(internal_caller);
} else if self.endpoint.requires_auth && !request.has_authenticated_user() {
return Error {
code: ErrCode::Unauthenticated,
Expand All @@ -519,7 +524,7 @@ impl EndpointHandler {
stack: None,
details: None,
}
.into_response();
.to_response(internal_caller);
}

let logger = crate::log::root();
Expand Down Expand Up @@ -576,13 +581,13 @@ impl EndpointHandler {
self.endpoint
.response
.encode(&payload)
.unwrap_or_else(|err| err.into_response()),
.unwrap_or_else(|err| err.to_response(internal_caller)),
Some(payload),
None,
),
ResponseData::Typed(Err(err)) => (
err.code.status_code().as_u16(),
err.as_ref().into_response(),
err.as_ref().to_response(internal_caller),
None,
Some(err),
),
Expand Down
28 changes: 28 additions & 0 deletions runtimes/core/src/api/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::str::FromStr;

use crate::error::{AppError, StackTrace};
use axum::extract::ws::rejection::WebSocketUpgradeRejection;
use serde::ser::SerializeStruct;
use serde::{Deserialize, Serialize};
use serde_with::{DeserializeFromStr, SerializeDisplay};

Expand All @@ -18,7 +19,28 @@ pub struct Error {
pub stack: Option<StackTrace>,
}

/// ErrorExternal hides internal information on `Error` when it serializes
#[derive(Debug)]
pub struct ExternalError<'a>(&'a Error);

impl Serialize for ExternalError<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut error = serializer.serialize_struct("Error", 3)?;
error.serialize_field("code", &self.0.code)?;
error.serialize_field("message", &self.0.message)?;
error.serialize_field("details", &self.0.details)?;
error.end()
}
}

impl Error {
pub fn as_external(&self) -> ExternalError {
ExternalError(self)
}

pub fn internal<E>(cause: E) -> Self
where
E: Into<anyhow::Error>,
Expand Down Expand Up @@ -129,6 +151,12 @@ impl Display for Error {
}
}

impl AsRef<Error> for Error {
fn as_ref(&self) -> &Self {
self
}
}

/// Represents an API Error.
#[derive(SerializeDisplay, DeserializeFromStr, Debug, Copy, Clone, PartialEq, Eq)]
pub enum ErrCode {
Expand Down
2 changes: 1 addition & 1 deletion runtimes/core/src/api/gateway/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ fn as_api_error(err: &pingora::Error) -> Option<&api::Error> {

fn api_error_response(err: &api::Error) -> (ResponseHeader, bytes::Bytes) {
let mut buf = BytesMut::with_capacity(128).writer();
serde_json::to_writer(&mut buf, &err).unwrap();
serde_json::to_writer(&mut buf, &err.as_external()).unwrap();

let mut resp = ResponseHeader::build(err.code.status_code(), Some(5)).unwrap();
resp.insert_header(header::SERVER, &pingora::protocols::http::SERVER_NAME[..])
Expand Down
5 changes: 2 additions & 3 deletions runtimes/core/src/api/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::future::{Future, IntoFuture};
use std::sync::{Arc, Mutex};

use anyhow::Context;
use axum::response::IntoResponse;

use crate::api::auth::{LocalAuthHandler, RemoteAuthHandler};
use crate::api::call::ServiceRegistry;
Expand All @@ -15,7 +14,7 @@ use crate::api::schema::encoding::EncodingConfig;
use crate::api::schema::JSONPayload;
use crate::api::{
auth, cors, encore_routes, endpoints_from_meta, jsonschema, paths, reqauth, server, APIResult,
Endpoint,
Endpoint, ToResponse,
};
use crate::encore::parser::meta::v1 as meta;
use crate::encore::runtime::v1 as runtime;
Expand Down Expand Up @@ -342,7 +341,7 @@ impl Manager {
stack: None,
details: None,
}
.into_response()
.to_response(None)
}

let encore_routes = encore_routes::Desc {
Expand Down
4 changes: 4 additions & 0 deletions runtimes/core/src/api/reqauth/caller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ impl Caller {
Gateway { .. } => false,
}
}

pub fn is_gateway(&self) -> bool {
matches!(&self, Caller::Gateway { .. })
}
}

impl FromStr for Caller {
Expand Down
8 changes: 3 additions & 5 deletions runtimes/core/src/api/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex, RwLock};

use axum::response::IntoResponse;

use crate::api;
use crate::api::endpoint::{EndpointHandler, SharedEndpointData};
use crate::api::paths::Pather;
use crate::api::reqauth::svcauth;
use crate::api::static_assets::StaticAssetsHandler;
use crate::api::{self, ToResponse};
use crate::api::{paths, reqauth, schema, BoxedHandler, EndpointMap};
use crate::encore::parser::meta::v1 as meta;
use crate::names::EndpointName;
Expand Down Expand Up @@ -60,7 +58,7 @@ impl Server {
stack: None,
details: None,
}
.into_response()
.to_response(None)
}

let mut fallback_router = axum::Router::new();
Expand Down Expand Up @@ -256,7 +254,7 @@ where
stack: None,
details: None,
}
.into_response();
.to_response(None);
std::task::Poll::Ready(resp)
}
}
Expand Down
5 changes: 2 additions & 3 deletions runtimes/core/src/pubsub/gcp/push_sub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ use std::sync::{Arc, RwLock};

use anyhow::{Context, Result};
use axum::extract::Request;
use axum::response::IntoResponse;
use axum::RequestExt;
use chrono::{DateTime, Utc};
use http_body_util::BodyExt;
use serde::Deserialize;

use crate::api::{self, APIResult};
use crate::api::{self, APIResult, ToResponse};
use crate::encore::runtime::v1 as pb;
use crate::pubsub::manager::SubHandler;
use crate::pubsub::{self, MessageId};
Expand Down Expand Up @@ -96,7 +95,7 @@ impl pubsub::PushRequestHandler for PushHandler {
Ok(()) => axum::response::Response::new(axum::body::Body::empty()),
Err(e) => {
log::error!("push handler returned error: {:?}", e);
e.into_response()
e.to_response(None)
}
}
})
Expand Down
11 changes: 5 additions & 6 deletions runtimes/core/src/pubsub/push_registry.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::{collections::HashMap, pin::Pin, sync::Arc};

use axum::{extract::Path, response::IntoResponse, RequestExt};
use axum::{extract::Path, RequestExt};
use futures::Future;
use std::sync::RwLock;

use crate::api::{self};
use crate::api::{self, ToResponse};

use super::PushRequestHandler;

Expand Down Expand Up @@ -54,7 +54,7 @@ impl PushHandlerRegistry {
"encore pub/sub push handler: unable to extract push id from path: {:?}",
e
);
return api::Error::internal(e).into_response();
return api::Error::internal(e).to_response(None);
}
};

Expand All @@ -64,9 +64,8 @@ impl PushHandlerRegistry {

match handler {
Some(handler) => handler.handle_push(req).await,
None => {
api::Error::not_found("no handler registered for push subscription").into_response()
}
None => api::Error::not_found("no handler registered for push subscription")
.to_response(None),
}
}
}
Expand Down
13 changes: 7 additions & 6 deletions runtimes/js/src/raw_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ use std::sync::Arc;

use axum::body::Body;
use axum::http::{Response, StatusCode};
use axum::response::IntoResponse;
use bytes::Bytes;
use napi::bindgen_prelude::{Buffer, Either3};
use napi::{Either, Env, JsFunction, JsUnknown, NapiRaw};
use napi_derive::napi;
use tokio::sync::{mpsc, oneshot};

use encore_runtime_core::api;
use encore_runtime_core::api::{self, ToResponse};

use crate::api::Request;
use crate::error::coerce_to_api_error;
Expand Down Expand Up @@ -343,9 +342,11 @@ impl api::BoxedHandler for JSRawHandler {
Box::pin(async move {
let (body_tx, mut body_rx) = oneshot::channel();

let internal_caller = req.internal_caller.clone();

let Some(body) = req.take_raw_body() else {
let err = api::Error::internal(anyhow::anyhow!("missing body"));
return api::ResponseData::Raw(err.into_response());
return api::ResponseData::Raw(err.to_response(internal_caller));
};

// Call the handler.
Expand Down Expand Up @@ -374,20 +375,20 @@ impl api::BoxedHandler for JSRawHandler {
Ok(resp) => resp,
Err(_) => {
let err_resp = api::Error::internal(anyhow::anyhow!("handler did not respond"));
err_resp.into_response()
err_resp.to_response(internal_caller)
}
}
}
err = err_rx.recv() => {
match err {
Some(Err(err)) => err.into_response(),
Some(Err(err)) => err.to_response(internal_caller),
_ => {
// We didn't get an error. Wait for the response body instead.
match body_rx.await {
Ok(resp) => resp,
Err(_) => {
let err_resp = api::Error::internal(anyhow::anyhow!("handler did not respond"));
err_resp.into_response()
err_resp.to_response(internal_caller)
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions runtimes/js/src/websocket_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ use crate::napi_util::PromiseHandler;
use crate::threadsafe_function::{
ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
};
use axum::response::IntoResponse;
use encore_runtime_core::api::websocket::StreamMessagePayload;
use encore_runtime_core::api::websocket_client;
use encore_runtime_core::api::{self, schema, APIResult, HandlerRequest};
use encore_runtime_core::api::{websocket_client, ToResponse};
use napi::{Env, JsFunction, NapiRaw};
use napi_derive::napi;
use std::future::Future;
Expand All @@ -30,6 +29,7 @@ impl api::BoxedHandler for JSWebSocketHandler {
req: HandlerRequest,
) -> Pin<Box<dyn Future<Output = api::ResponseData> + Send + 'static>> {
Box::pin(async move {
let internal_caller = req.internal_caller.clone();
let resp = api::websocket::upgrade_request(req, |req, payload, tx| async move {
self.handler.call(
WsRequestMessage {
Expand All @@ -43,7 +43,7 @@ impl api::BoxedHandler for JSWebSocketHandler {

match resp {
Ok(resp) => api::ResponseData::Raw(resp),
Err(e) => api::ResponseData::Raw(e.into_response()),
Err(e) => api::ResponseData::Raw(e.to_response(internal_caller)),
}
})
}
Expand Down
Loading