Compare commits

...
18 changed files with 237 additions and 104 deletions
Generated
+1
View File
@@ -11542,6 +11542,7 @@ dependencies = [
"elasticsearch",
"elasticsearch_schema",
"enums",
"erased-serde",
"error_utils",
"errors",
"fal_client",
+1
View File
@@ -334,6 +334,7 @@ regex = "1.12.3"
reqwest = { version = "0.12.23", default-features = false, features = [ "charset", "http2", "system-proxy", "json", "cookies", "rustls-tls", "gzip" ] }
reqwest-websocket = { version = "0.5.0" }
rust-s3 = { version = "0.36.0-beta.2", default-features = false, features = ["fail-on-err", "tags", "tokio-rustls-tls"] }
erased-serde = "0.4"
serde = { version = "1.0.228", features = ["derive"] }
serde_derive = "1.0.228"
serde_json = "1.0.149"
@@ -175,6 +175,7 @@ magic-crypt = "3.1.13" # encrypting sort IDs for the frontend so we don't leak i
md-5 = "0.10.6" # gravatar
# Serialization
erased-serde.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
@@ -897,8 +897,6 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
GetWeightError,
GetWeightPathInfo,
GetWeightResponse,
GoogleCreateAccountErrorResponse,
GoogleCreateAccountErrorType,
GoogleCreateAccountRequest,
GoogleCreateAccountSuccessResponse,
InferTtsError,
@@ -9,6 +9,7 @@ use actix_http::StatusCode;
use actix_web::{HttpResponse, HttpResponseBuilder, ResponseError};
use anyhow::anyhow;
use mysql_queries::errors::mysql_error::{MysqlCrateErrorSubtype, MysqlError};
use serde::Serialize;
/// An error type for actix-web handlers that wraps causal errors for debugging
/// and paging while presenting safe, generic HTTP responses to users.
@@ -37,6 +38,18 @@ pub enum CommonWebError {
/// 400 Bad Request with a user-facing message.
BadInputWithSimpleMessage(String),
/// 400 Bad Request with an endpoint-specific JSON body.
/// The caller hands over any `Serialize` struct; the middleware writes it
/// directly as the response body with `Content-Type: application/json`.
/// Construct via [`CommonWebError::bad_input_tailored_response`].
///
/// Stored as `Arc<dyn erased_serde::Serialize + Send + Sync + 'static>`:
/// `serde::Serialize` isn't object-safe (its method is generic over
/// `Serializer`), so we use `erased_serde::Serialize` — a sibling trait
/// with a non-generic method, auto-implemented for every `T: Serialize`.
/// `Arc` (instead of `Box`) keeps the enum `Clone`.
BadInputTailoredResponse(Arc<dyn erased_serde::Serialize + Send + Sync + 'static>),
/// 401 Unauthorized.
NotAuthorized,
@@ -96,6 +109,24 @@ impl CommonWebError {
Self::from_anyhow_error(anyhow!("ServerErrorWithMessage: {:?}", msg))
}
/// Build a 400 Bad Request whose body is the JSON-serialized form of `body`.
///
/// Use this when an endpoint needs to return a structured validation payload
/// (per-field errors, etc.) rather than the generic `{success, error_code, message}`
/// envelope used by [`BadInputWithSimpleMessage`]. The caller passes its own
/// `Serialize` type directly — no envelope, no `serde_json::Value` boxing
/// at the callsite.
/// Build a 400 Bad Request whose body is the JSON-serialized form of `body`.
///
/// The caller passes its typed struct directly. Serialization is deferred to
/// response time, when the middleware writes the JSON straight onto the wire.
pub fn bad_input_tailored_response<T>(body: T) -> Self
where
T: Serialize + Send + Sync + 'static,
{
Self::BadInputTailoredResponse(Arc::new(body))
}
/// Extract the wrapped causal error (only present for `UncaughtServerError`).
pub fn cause(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> {
match self {
@@ -131,6 +162,12 @@ impl Display for CommonWebError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::BadInputWithSimpleMessage(msg) => write!(f, "Bad input: {}", msg),
Self::BadInputTailoredResponse(payload) => {
match serde_json::to_string(payload) {
Ok(s) => write!(f, "Bad input (tailored): {}", s),
Err(_) => write!(f, "Bad input (tailored): <unserializable payload>"),
}
}
Self::NotAuthorized => write!(f, "Not authorized"),
Self::NotFound => write!(f, "Not found"),
Self::PaymentRequired => write!(f, "Payment required"),
@@ -149,6 +186,12 @@ impl std::fmt::Debug for CommonWebError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::BadInputWithSimpleMessage(msg) => write!(f, "BadInputWithSimpleMessage({:?})", msg),
Self::BadInputTailoredResponse(payload) => {
match serde_json::to_string(payload) {
Ok(s) => write!(f, "BadInputTailoredResponse({})", s),
Err(_) => write!(f, "BadInputTailoredResponse(<unserializable>)"),
}
}
Self::NotAuthorized => write!(f, "NotAuthorized"),
Self::NotFound => write!(f, "NotFound"),
Self::PaymentRequired => write!(f, "PaymentRequired"),
@@ -179,6 +222,7 @@ impl ResponseError for CommonWebError {
fn status_code(&self) -> StatusCode {
match self {
Self::BadInputWithSimpleMessage(_) => StatusCode::BAD_REQUEST,
Self::BadInputTailoredResponse(_) => StatusCode::BAD_REQUEST,
Self::NotAuthorized => StatusCode::UNAUTHORIZED,
Self::NotFound => StatusCode::NOT_FOUND,
Self::PaymentRequired => StatusCode::PAYMENT_REQUIRED,
@@ -203,6 +247,9 @@ impl ResponseError for CommonWebError {
message: msg,
})
}
Self::BadInputTailoredResponse(payload) => {
HttpResponse::BadRequest().json(payload)
}
Self::ContentPolicyRejected => {
HttpResponseBuilder::new(status)
.json(JsonErrorWithMessage {
@@ -387,6 +434,32 @@ mod tests {
assert!(body.contains("\"error_code\":400"));
}
#[test]
fn bad_input_tailored_response_serializes_payload_verbatim() {
#[derive(Serialize)]
struct Sample {
reason: &'static str,
count: u32,
}
let error = CommonWebError::bad_input_tailored_response(Sample {
reason: "no good",
count: 3,
});
assert_eq!(error.status_code(), StatusCode::BAD_REQUEST);
assert!(!error.is_server_error());
assert!(error.cause().is_none());
let response = error.error_response();
let content_type = response.headers().get("content-type").unwrap().to_str().unwrap();
assert!(content_type.starts_with("application/json"));
let bytes = response.into_body().try_into_bytes().unwrap();
let body = String::from_utf8(bytes.to_vec()).unwrap();
// Body is the raw serialized payload — no envelope.
assert_eq!(body, r#"{"reason":"no good","count":3}"#);
}
#[test]
fn not_found_returns_404() {
let error = CommonWebError::NotFound;
@@ -0,0 +1,17 @@
use actix_web::HttpResponse;
use crate::http_server::common_responses::common_web_error::CommonWebError;
/// Dev-only endpoint that unconditionally returns a `BadInputWithSimpleMessage`
/// so we can exercise error paths (paging, response shape, etc.) end-to-end.
#[utoipa::path(
get,
tag = "Dev",
path = "/v1/dev/basic_input_error",
responses(
(status = 400, description = "Always returns this — that's the point"),
),
)]
pub async fn dev_basic_input_error_handler() -> Result<HttpResponse, CommonWebError> {
Err(CommonWebError::BadInputWithSimpleMessage("testing".to_string()))
}
@@ -0,0 +1,55 @@
use actix_web::HttpResponse;
use serde_derive::Serialize;
use crate::http_server::common_responses::common_web_error::CommonWebError;
/// Dev-only endpoint that unconditionally returns a `BadInputTailoredResponse`
/// carrying a per-field validation payload, so we can exercise the tailored
/// 400 response shape end-to-end.
#[utoipa::path(
get,
tag = "Dev",
path = "/v1/dev/input_error",
responses(
(status = 400, description = "Always returns this — that's the point"),
),
)]
pub async fn dev_input_error_handler() -> Result<HttpResponse, CommonWebError> {
let body = BadInputError {
fields: vec![
BadFieldMessage {
field_name: "username".to_string(),
message: "username is taken".to_string(),
max_length: Some(32),
min_length: Some(3),
},
BadFieldMessage {
field_name: "email".to_string(),
message: "email is invalid".to_string(),
max_length: None,
min_length: None,
},
BadFieldMessage {
field_name: "password".to_string(),
message: "password is too short".to_string(),
max_length: Some(128),
min_length: Some(8),
},
],
};
Err(CommonWebError::bad_input_tailored_response(body))
}
#[derive(Serialize)]
struct BadInputError {
fields: Vec<BadFieldMessage>,
}
#[derive(Serialize)]
struct BadFieldMessage {
field_name: String,
message: String,
max_length: Option<usize>,
min_length: Option<usize>,
}
@@ -0,0 +1,2 @@
pub mod dev_basic_input_error_handler;
pub mod dev_input_error_handler;
@@ -5,6 +5,7 @@ pub mod billing_fakeyou;
pub mod characters;
pub mod comments;
pub mod credits;
pub mod dev;
pub mod download_job;
pub mod featured_items;
pub mod generate;
@@ -1,4 +1,5 @@
use crate::http_server::endpoints::users::google_sso::google_sso_handler::{GoogleCreateAccountErrorResponse, GoogleCreateAccountRequest};
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::endpoints::users::google_sso::google_sso_handler::GoogleCreateAccountRequest;
use crate::state::certs::google_sign_in_cert::GoogleSignInCert;
use google_sign_in::claims::claims::Claims;
use google_sign_in::decode_and_verify_token_claims::decode_and_verify_token_claims;
@@ -9,12 +10,12 @@ use std::collections::HashSet;
pub async fn check_claims(
request: &GoogleCreateAccountRequest,
google_sign_in_cert: &GoogleSignInCert,
) -> Result<Claims, GoogleCreateAccountErrorResponse> {
) -> Result<Claims, CommonWebError> {
let keys = google_sign_in_cert.fetch_key_map(false)
.await
.map_err(|e| {
warn!("error downloading google certs: {:?}", e);
GoogleCreateAccountErrorResponse::server_error()
CommonWebError::from_anyhow_error(e)
})?;
let verification_options = Some(build_options());
@@ -28,7 +29,7 @@ pub async fn check_claims(
.await
.map_err(|e| {
warn!("error refreshing google certs: {:?}", e);
GoogleCreateAccountErrorResponse::server_error()
CommonWebError::from_anyhow_error(e)
})?;
let verification_options = Some(build_options());
@@ -36,7 +37,9 @@ pub async fn check_claims(
let claims = decode_and_verify_token_claims(&keys, &request.google_credential, verification_options)
.map_err(|e| {
warn!("error decoding google token claims: {:?}", e);
GoogleCreateAccountErrorResponse::bad_request()
// The detailed error is logged above; the user-facing message is intentionally
// generic so we don't leak JWT internals.
CommonWebError::BadInputWithSimpleMessage("invalid google credential".to_string())
})?;
claims
@@ -3,24 +3,19 @@
//#![forbid(unused_mut)]
//#![forbid(unused_variables)]
use std::fmt;
use std::fmt::Formatter;
use crate::util::lookup::resolve_referral_info::resolve_referral_info;
use mysql_queries::queries::user_referrals::insert_user_referral::{insert_user_referral, InsertUserReferralArgs};
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::endpoints::users::google_sso::check_claims::check_claims;
use crate::http_server::endpoints::users::google_sso::handle_existing_sso_account::{handle_existing_sso_account, ExistingAccountArgs};
use crate::http_server::endpoints::users::google_sso::handle_new_sso_account::{handle_new_sso_account, NewSsoArgs};
use crate::state::certs::google_sign_in_cert::GoogleSignInCert;
use crate::util::lookup::resolve_referral_info::resolve_referral_info;
use actix_artcraft::sessions::user_sessions::http_user_session_manager::HttpUserSessionManager;
use actix_web::error::ResponseError;
use actix_web::http::StatusCode;
use actix_web::web::{Data, Json};
use actix_web::{HttpRequest, HttpResponse};
use http_server_common::request::get_request_ip::get_request_ip;
use http_server_common::response::serialize_as_json_error::serialize_as_json_error;
use log::{info, warn};
use mysql_queries::queries::google_sign_in_accounts::get_google_sign_in_account_by_subject::get_google_sign_in_account;
use mysql_queries::queries::user_referrals::insert_user_referral::{insert_user_referral, InsertUserReferralArgs};
use mysql_queries::queries::users::user_sessions::create_user_session_with_transactor::create_user_session_with_transactor;
use mysql_queries::utils::transactor::Transactor;
use sqlx::{Acquire, MySqlPool};
@@ -118,62 +113,6 @@ pub struct GoogleCreateAccountSuccessResponse {
pub maybe_user_display_name: Option<String>,
}
#[derive(ToSchema, Serialize, Debug)]
pub struct GoogleCreateAccountErrorResponse {
pub success: bool,
pub error_type: GoogleCreateAccountErrorType,
}
#[derive(ToSchema, Copy, Clone, Debug, Serialize)]
pub enum GoogleCreateAccountErrorType {
BadRequest, // Other request malformed errors, eg. bad Origin header
BadInput,
EmailTaken,
ServerError,
UsernameReserved,
UsernameTaken,
}
impl GoogleCreateAccountErrorResponse {
pub fn server_error() -> Self {
Self {
success: false,
error_type: GoogleCreateAccountErrorType::ServerError,
}
}
pub fn bad_request() -> Self {
Self {
success: false,
error_type: GoogleCreateAccountErrorType::BadRequest,
}
}
}
// NB: Not using DeriveMore since Clion doesn't understand it.
impl fmt::Display for GoogleCreateAccountErrorResponse {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.error_type)
}
}
impl ResponseError for GoogleCreateAccountErrorResponse {
fn status_code(&self) -> StatusCode {
match self.error_type {
GoogleCreateAccountErrorType::BadRequest => StatusCode::BAD_REQUEST,
GoogleCreateAccountErrorType::BadInput => StatusCode::BAD_REQUEST,
GoogleCreateAccountErrorType::EmailTaken => StatusCode::BAD_REQUEST,
GoogleCreateAccountErrorType::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
GoogleCreateAccountErrorType::UsernameReserved => StatusCode::BAD_REQUEST,
GoogleCreateAccountErrorType::UsernameTaken => StatusCode::BAD_REQUEST,
}
}
fn error_response(&self) -> HttpResponse {
serialize_as_json_error(self)
}
}
/// Sign in or sign up with "Sign in with Google" credentials.
/// This supports both sign up for new users and sign in for existing users.
#[utoipa::path(
@@ -182,9 +121,9 @@ impl ResponseError for GoogleCreateAccountErrorResponse {
path = "/v1/accounts/google_sso",
responses(
(status = 200, description = "Success", body = GoogleCreateAccountSuccessResponse),
(status = 400, description = "Bad input", body = GoogleCreateAccountErrorResponse),
(status = 401, description = "Not authorized", body = GoogleCreateAccountErrorResponse),
(status = 500, description = "Server error", body = GoogleCreateAccountErrorResponse),
(status = 400, description = "Bad input", body = CommonWebError),
(status = 401, description = "Not authorized", body = CommonWebError),
(status = 500, description = "Server error", body = CommonWebError),
),
params(
("request" = GoogleCreateAccountRequest, description = "Payload for Request"),
@@ -196,7 +135,7 @@ pub async fn google_sso_handler(
mysql_pool: Data<MySqlPool>,
session_cookie_manager: Data<HttpUserSessionManager>,
google_sign_in_cert: Data<GoogleSignInCert>,
) -> Result<HttpResponse, GoogleCreateAccountErrorResponse>
) -> Result<HttpResponse, CommonWebError>
{
let claims = check_claims(&request, &google_sign_in_cert).await?;
@@ -208,28 +147,28 @@ pub async fn google_sso_handler(
.map(|s| s.to_string())
.ok_or_else(|| {
warn!("no subject in google claims");
GoogleCreateAccountErrorResponse::bad_request()
CommonWebError::BadInputWithSimpleMessage("no subject in google claims".to_string())
})?;
let claims_email_address = claims.email()
.map(|email| email.to_string())
.ok_or_else(|| {
warn!("no email address in google claims");
GoogleCreateAccountErrorResponse::bad_request()
CommonWebError::BadInputWithSimpleMessage("no email address in google claims".to_string())
})?;
let mut mysql_connection = mysql_pool.acquire()
.await
.map_err(|e| {
warn!("Could not acquire DB pool: {:?}", e);
GoogleCreateAccountErrorResponse::server_error()
CommonWebError::from_error(e)
})?;
let maybe_sso_account = get_google_sign_in_account(&claims_subject, &mut *mysql_connection)
.await
.map_err(|err| {
warn!("error getting google sign in account: {:?}", err);
GoogleCreateAccountErrorResponse::server_error()
CommonWebError::from_anyhow_error(err)
})?;
let user_token;
@@ -312,7 +251,7 @@ pub async fn google_sso_handler(
.await
.map_err(|e| {
warn!("error creating user session: {:?}", e);
GoogleCreateAccountErrorResponse::server_error()
CommonWebError::from_anyhow_error(e)
})?;
info!("new user session created");
@@ -339,16 +278,16 @@ pub fn construct_http_response(
user_token: &UserToken,
maybe_user_display_name: Option<String>,
username_not_yet_customized: bool,
) -> Result<HttpResponse, GoogleCreateAccountErrorResponse> {
) -> Result<HttpResponse, CommonWebError> {
let session_cookie = match session_cookie_manager.create_cookie(&session_token, &user_token) {
Ok(cookie) => cookie,
Err(_) => return Err(GoogleCreateAccountErrorResponse::server_error()),
Err(err) => return Err(CommonWebError::from_error(err)),
};
let signed_session = match session_cookie_manager.encode_session_payload(&session_token, &user_token) {
Ok(payload) => payload,
Err(_) => return Err(GoogleCreateAccountErrorResponse::server_error()),
Err(err) => return Err(CommonWebError::from_error(err)),
};
let response = GoogleCreateAccountSuccessResponse {
@@ -358,8 +297,7 @@ pub fn construct_http_response(
maybe_user_display_name,
};
let body = serde_json::to_string(&response)
.map_err(|_e| GoogleCreateAccountErrorResponse::server_error())?;
let body = serde_json::to_string(&response)?;
Ok(HttpResponse::Ok()
.cookie(session_cookie)
@@ -1,4 +1,4 @@
use crate::http_server::endpoints::users::google_sso::google_sso_handler::GoogleCreateAccountErrorResponse;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use actix_web::HttpRequest;
use google_sign_in::claims::claims::Claims;
use http_server_common::request::get_request_ip::get_request_ip;
@@ -21,7 +21,7 @@ pub struct ExistingAccountArgs<'a> {
pub async fn handle_existing_sso_account(
args: ExistingAccountArgs<'_>
)
-> Result<UserToken, GoogleCreateAccountErrorResponse>
-> Result<UserToken, CommonWebError>
{
let user_token = match &args.sso_account.maybe_user_token {
Some(token) => token.clone(),
@@ -30,7 +30,9 @@ pub async fn handle_existing_sso_account(
// consider how to migrate accounts and handle all the various account states.
// For now, we'll just deny this possibility. It should not happen.
warn!("no user token for existing google sign in account!");
return Err(GoogleCreateAccountErrorResponse::server_error());
return Err(CommonWebError::server_error_with_message(
"no user token for existing google sign in account",
));
},
};
@@ -1,4 +1,4 @@
use crate::http_server::endpoints::users::google_sso::google_sso_handler::GoogleCreateAccountErrorResponse;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::endpoints::users::google_sso::handle_new_sso_account_for_existing_user::{handle_new_sso_account_for_existing_user, LinkArgs};
use crate::http_server::endpoints::users::google_sso::handle_new_sso_account_for_new_user::{handle_new_sso_account_for_new_user, CreateArgs};
use crate::util::canonicalize_email_for_users_table::canonicalize_email_for_users_table;
@@ -33,7 +33,7 @@ pub struct NewSsoAccountInfo {
pub async fn handle_new_sso_account(
args: NewSsoArgs<'_>
)
-> Result<NewSsoAccountInfo, GoogleCreateAccountErrorResponse>
-> Result<NewSsoAccountInfo, CommonWebError>
{
// NB: We use this routine to "normalize" email addresses in the user table.
// It won't necessarily match the email address in the Google claims.
@@ -46,7 +46,7 @@ pub async fn handle_new_sso_account(
.await
.map_err(|err| {
warn!("error looking up user by email: {:?}", err);
GoogleCreateAccountErrorResponse::server_error()
CommonWebError::from_anyhow_error(err)
})?;
match maybe_user_account {
@@ -1,4 +1,4 @@
use crate::http_server::endpoints::users::google_sso::google_sso_handler::GoogleCreateAccountErrorResponse;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::endpoints::users::google_sso::handle_new_sso_account::NewSsoAccountInfo;
pub use actix_web::HttpRequest;
use google_sign_in::claims::claims::Claims;
@@ -20,13 +20,13 @@ pub struct LinkArgs<'a> {
pub async fn handle_new_sso_account_for_existing_user(
args: LinkArgs<'_>
)
-> Result<NewSsoAccountInfo, GoogleCreateAccountErrorResponse>
-> Result<NewSsoAccountInfo, CommonWebError>
{
let mut transaction = args.mysql_connection.begin()
.await
.map_err(|e| {
warn!("Could not begin transaction: {:?}", e);
GoogleCreateAccountErrorResponse::server_error()
CommonWebError::from_error(e)
})?;
let ip_address = get_request_ip(&args.http_request);
@@ -44,14 +44,14 @@ pub async fn handle_new_sso_account_for_existing_user(
transaction: &mut transaction,
}).await.map_err(|err| {
warn!("error inserting google sign in account: {:?}", err);
GoogleCreateAccountErrorResponse::server_error()
CommonWebError::from_anyhow_error(err)
})?;
transaction.commit()
.await
.map_err(|e| {
warn!("Could not commit transaction: {:?}", e);
GoogleCreateAccountErrorResponse::server_error()
CommonWebError::from_error(e)
})?;
Ok(NewSsoAccountInfo {
@@ -1,4 +1,4 @@
use crate::http_server::endpoints::users::google_sso::google_sso_handler::GoogleCreateAccountErrorResponse;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::endpoints::users::google_sso::handle_new_sso_account::NewSsoAccountInfo;
use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags;
use actix_artcraft::requests::get_request_signup_source_enum::get_request_signup_source_enum;
@@ -31,13 +31,13 @@ pub struct CreateArgs<'a> {
pub async fn handle_new_sso_account_for_new_user(
args: CreateArgs<'_>
)
-> Result<NewSsoAccountInfo, GoogleCreateAccountErrorResponse>
-> Result<NewSsoAccountInfo, CommonWebError>
{
let mut transaction = args.mysql_connection.begin()
.await
.map_err(|e| {
warn!("Could not begin transaction: {:?}", e);
GoogleCreateAccountErrorResponse::server_error()
CommonWebError::from_error(e)
})?;
// Enroll users in studio temporarily.
@@ -87,19 +87,26 @@ pub async fn handle_new_sso_account_for_new_user(
},
Err(err) => {
warn!("error creating account from google sso: {:?}", err);
return Err(GoogleCreateAccountErrorResponse::server_error());
// CreateAccountError doesn't impl std::error::Error, so fold it into anyhow.
return Err(CommonWebError::from_anyhow_error(
anyhow::anyhow!("error creating account from google sso: {:?}", err),
));
},
}
}
let user_token = maybe_user_token.ok_or_else(|| {
error!("no username without collision after several tries (token)");
GoogleCreateAccountErrorResponse::server_error()
CommonWebError::server_error_with_message(
"no username without collision after several tries (token)",
)
})?;
let user_display_name = maybe_user_display_name.ok_or_else(|| {
error!("no username without collision after several tries (display name)");
GoogleCreateAccountErrorResponse::server_error()
CommonWebError::server_error_with_message(
"no username without collision after several tries (display name)",
)
})?;
let _token = insert_google_sign_in_account(InsertGoogleSignInArgs {
@@ -115,14 +122,14 @@ pub async fn handle_new_sso_account_for_new_user(
transaction: &mut transaction,
}).await.map_err(|err| {
warn!("error inserting google sign in account: {:?}", err);
GoogleCreateAccountErrorResponse::server_error()
CommonWebError::from_anyhow_error(err)
})?;
transaction.commit()
.await
.map_err(|e| {
warn!("Could not commit transaction: {:?}", e);
GoogleCreateAccountErrorResponse::server_error()
CommonWebError::from_error(e)
})?;
Ok(NewSsoAccountInfo {
@@ -3,6 +3,7 @@ use crate::http_server::routes::application_routes::billing_fakeyou_routes::add_
use crate::http_server::routes::application_routes::character_routes::add_character_routes;
use crate::http_server::routes::application_routes::comments_routes::add_comments_routes;
use crate::http_server::routes::application_routes::credits_routes::add_credits_routes;
use crate::http_server::routes::application_routes::dev_routes::add_dev_routes;
use crate::http_server::routes::application_routes::featured_item_routes::add_featured_item_routes;
use crate::http_server::routes::application_routes::generate_routes::add_generate_routes;
use crate::http_server::routes::application_routes::job_routes::add_job_routes;
@@ -63,6 +64,7 @@ where
// Other useful tools
app = add_analytics_routes(app); // /v1/analytics/...
app = add_web_referrals_routes(app); // /v1/web_referrals/...
app = add_dev_routes(app); // /v1/dev/... — dev-only error/response shape probes
// User and user-adjacent routes
app = add_comments_routes(app); // /v1/comments/...
@@ -0,0 +1,31 @@
use actix_http::body::MessageBody;
use actix_service::ServiceFactory;
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::error::Error;
use actix_web::{web, App, HttpResponse};
use crate::http_server::endpoints::dev::dev_basic_input_error_handler::dev_basic_input_error_handler;
use crate::http_server::endpoints::dev::dev_input_error_handler::dev_input_error_handler;
pub fn add_dev_routes<T, B> (app: App<T>) -> App<T>
where
B: MessageBody,
T: ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
{
app.service(web::scope("/v1/dev")
.service(web::resource("/input_error")
.route(web::get().to(dev_input_error_handler))
.route(web::head().to(|| HttpResponse::Ok()))
)
.service(web::resource("/basic_input_error")
.route(web::get().to(dev_basic_input_error_handler))
.route(web::head().to(|| HttpResponse::Ok()))
)
)
}
@@ -3,6 +3,7 @@ mod billing_fakeyou_routes;
mod character_routes;
mod comments_routes;
mod credits_routes;
mod dev_routes;
mod featured_item_routes;
mod generate_routes;
mod job_routes;