use anyhow::Context as _;
use axum::extract::DefaultBodyLimit;
use axum::routing::options;
use axum::{Extension, Router};
use axum_extra::TypedHeader;
use bytes::Bytes;
use surrealdb_core::dbs::Session;
use surrealdb_core::dbs::capabilities::RouteTarget;
use surrealdb_core::iam::Token;
use surrealdb_core::syn;
use surrealdb_types::{SurrealValue, Value};
use tower_http::limit::RequestBodyLimitLayer;
use super::AppState;
use super::error::ResponseError;
use super::headers::Accept;
use super::output::Output;
use crate::cnf::HTTP_MAX_SIGNIN_BODY_SIZE;
use crate::ntw::error::Error as NetError;
use crate::ntw::input::bytes_to_utf8;
#[derive(SurrealValue)]
struct Success {
code: u16,
details: String,
token: Token,
}
impl Success {
fn new(token: Token) -> Success {
Success {
token,
code: 200,
details: String::from("Authentication succeeded"),
}
}
}
pub fn router<S>() -> Router<S>
where
S: Clone + Send + Sync + 'static,
{
Router::new()
.route("/signup", options(|| async {}).post(handler))
.route_layer(DefaultBodyLimit::disable())
.layer(RequestBodyLimitLayer::new(*HTTP_MAX_SIGNIN_BODY_SIZE))
}
async fn handler(
Extension(state): Extension<AppState>,
Extension(mut session): Extension<Session>,
accept: Option<TypedHeader<Accept>>,
body: Bytes,
) -> Result<Output, ResponseError> {
let kvs = &state.datastore;
if !kvs.allows_http_route(&RouteTarget::Signup) {
warn!("Capabilities denied HTTP route request attempt, target: '{}'", &RouteTarget::Signup);
return Err(NetError::ForbiddenRoute(RouteTarget::Signup.to_string()).into());
}
let data = bytes_to_utf8(&body).context("Non UTF-8 request body").map_err(ResponseError)?;
match syn::json(data) {
Ok(Value::Object(vars)) => {
match surrealdb_core::iam::signup::signup(kvs, &mut session, vars.into()).await {
Ok(token) => {
match accept.as_deref() {
Some(Accept::ApplicationJson) => {
let success = Success::new(token).into_value().into_json_value();
Ok(Output::json_other(&success))
}
Some(Accept::ApplicationCbor) => {
let success = Success::new(token).into_value();
Ok(Output::cbor(success))
}
Some(Accept::TextPlain) => {
let token = match token {
Token::Access(token) => token,
Token::WithRefresh {
access: token,
..
} => token,
};
Ok(Output::Text(token))
}
Some(Accept::ApplicationFlatbuffers) => {
let success = Success::new(token).into_value();
Ok(Output::flatbuffers(&success))
}
None => Ok(Output::None),
_ => Err(NetError::InvalidType.into()),
}
}
Err(err) => Err(ResponseError(err)),
}
}
_ => Err(NetError::Request.into()),
}
}