check params using flowy-user entities

This commit is contained in:
appflowy 2021-08-23 23:02:42 +08:00
parent f05f0c43a3
commit d6c761917b
11 changed files with 60 additions and 51 deletions

View file

@ -4,7 +4,7 @@ use crate::{
}; };
use chrono::{Duration, Local}; use chrono::{Duration, Local};
use derive_more::{From, Into}; use derive_more::{From, Into};
use flowy_net::errors::{Code, ServerError}; use flowy_net::errors::{ErrorCode, ServerError};
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -51,7 +51,7 @@ impl Token {
&EncodingKey::from_secret(jwt_secret().as_ref()), &EncodingKey::from_secret(jwt_secret().as_ref()),
) )
.map(Into::into) .map(Into::into)
.map_err(|err| ServerError::internal().with_msg(err)) .map_err(|err| ServerError::internal().context(err))
} }
pub fn decode_token(token: &Self) -> Result<Claim, ServerError> { pub fn decode_token(token: &Self) -> Result<Claim, ServerError> {
@ -61,6 +61,6 @@ impl Token {
&Validation::new(DEFAULT_ALGORITHM), &Validation::new(DEFAULT_ALGORITHM),
) )
.map(|data| Ok(data.claims)) .map(|data| Ok(data.claims))
.map_err(|err| ServerError::unauthorized().with_msg(err))? .map_err(|err| ServerError::unauthorized().context(err))?
} }
} }

View file

@ -1,7 +1,7 @@
use crate::config::MAX_PAYLOAD_SIZE; use crate::config::MAX_PAYLOAD_SIZE;
use actix_web::web; use actix_web::web;
use flowy_net::{ use flowy_net::{
errors::{Code, ServerError}, errors::{ErrorCode, ServerError},
response::*, response::*,
}; };
use futures::StreamExt; use futures::StreamExt;
@ -23,11 +23,11 @@ pub fn parse_from_bytes<T: Message>(bytes: &[u8]) -> Result<T, ServerError> {
pub async fn poll_payload(mut payload: web::Payload) -> Result<web::BytesMut, ServerError> { pub async fn poll_payload(mut payload: web::Payload) -> Result<web::BytesMut, ServerError> {
let mut body = web::BytesMut::new(); let mut body = web::BytesMut::new();
while let Some(chunk) = payload.next().await { while let Some(chunk) = payload.next().await {
let chunk = chunk.map_err(|err| ServerError::internal().with_msg(err))?; let chunk = chunk.map_err(|err| ServerError::internal().context(err))?;
if (body.len() + chunk.len()) > MAX_PAYLOAD_SIZE { if (body.len() + chunk.len()) > MAX_PAYLOAD_SIZE {
return Err(ServerError { return Err(ServerError {
code: Code::PayloadOverflow, code: ErrorCode::PayloadOverflow,
msg: "Payload overflow".to_string(), msg: "Payload overflow".to_string(),
}); });
} }

View file

@ -6,11 +6,12 @@ use actix_identity::Identity;
use anyhow::Context; use anyhow::Context;
use chrono::Utc; use chrono::Utc;
use flowy_net::{ use flowy_net::{
errors::{Code, ServerError}, errors::{ErrorCode, ServerError},
response::FlowyResponse, response::FlowyResponse,
}; };
use flowy_user::{ use flowy_user::{
entities::{SignInResponse, SignUpResponse}, entities::{SignInResponse, SignUpResponse},
prelude::parser::{UserEmail, UserPassword},
protobuf::{SignInParams, SignUpParams}, protobuf::{SignInParams, SignUpParams},
}; };
use sqlx::{Error, PgPool, Postgres, Transaction}; use sqlx::{Error, PgPool, Postgres, Transaction};
@ -21,17 +22,23 @@ pub async fn sign_in(
params: SignInParams, params: SignInParams,
id: Identity, id: Identity,
) -> Result<FlowyResponse, ServerError> { ) -> Result<FlowyResponse, ServerError> {
let email =
UserEmail::parse(params.email).map_err(|e| ServerError::params_invalid().context(e))?;
let password = UserPassword::parse(params.password)
.map_err(|e| ServerError::params_invalid().context(e))?;
let mut transaction = pool let mut transaction = pool
.begin() .begin()
.await .await
.context("Failed to acquire a Postgres connection to sign in")?; .context("Failed to acquire a Postgres connection to sign in")?;
let user = read_user(&mut transaction, &params.email).await?;
let user = read_user(&mut transaction, &email.0).await?;
transaction transaction
.commit() .commit()
.await .await
.context("Failed to commit SQL transaction to sign in.")?; .context("Failed to commit SQL transaction to sign in.")?;
match verify_password(&params.password, &user.password) { match verify_password(&password.0, &user.password) {
Ok(true) => { Ok(true) => {
let token = Token::create_token(&user)?; let token = Token::create_token(&user)?;
let data = SignInResponse { let data = SignInResponse {
@ -43,7 +50,7 @@ pub async fn sign_in(
id.remember(data.token.clone()); id.remember(data.token.clone());
FlowyResponse::success(data) FlowyResponse::success(data)
}, },
_ => Err(ServerError::passwordNotMatch()), _ => Err(ServerError::password_not_match()),
} }
} }
@ -77,11 +84,11 @@ async fn is_email_exist(
.bind(email) .bind(email)
.fetch_optional(transaction) .fetch_optional(transaction)
.await .await
.map_err(|err| ServerError::internal().with_msg(err))?; .map_err(|err| ServerError::internal().context(err))?;
match result { match result {
Some(_) => Err(ServerError { Some(_) => Err(ServerError {
code: Code::EmailAlreadyExists, code: ErrorCode::EmailAlreadyExists,
msg: format!("{} already exists", email), msg: format!("{} already exists", email),
}), }),
None => Ok(()), None => Ok(()),
@ -96,7 +103,7 @@ async fn read_user(
.bind(email) .bind(email)
.fetch_one(transaction) .fetch_one(transaction)
.await .await
.map_err(|err| ServerError::internal().with_msg(err))?; .map_err(|err| ServerError::internal().context(err))?;
Ok(user) Ok(user)
} }
@ -120,7 +127,7 @@ async fn insert_user(
) )
.execute(transaction) .execute(transaction)
.await .await
.map_err(|e| ServerError::internal().with_msg(e))?; .map_err(|e| ServerError::internal().context(e))?;
let data = SignUpResponse { let data = SignUpResponse {
uid: uuid.to_string(), uid: uuid.to_string(),

View file

@ -1,5 +1,5 @@
use bcrypt::{hash, verify, BcryptError, DEFAULT_COST}; use bcrypt::{hash, verify, BcryptError, DEFAULT_COST};
use flowy_net::errors::{Code, ServerError}; use flowy_net::errors::{ErrorCode, ServerError};
use jsonwebtoken::Algorithm; use jsonwebtoken::Algorithm;
pub fn uuid() -> String { uuid::Uuid::new_v4().to_string() } pub fn uuid() -> String { uuid::Uuid::new_v4().to_string() }
@ -10,14 +10,14 @@ pub fn hash_password(plain: &str) -> Result<String, ServerError> {
.and_then(|c| c.parse().ok()) .and_then(|c| c.parse().ok())
.unwrap_or(DEFAULT_COST); .unwrap_or(DEFAULT_COST);
hash(plain, hashing_cost).map_err(|e| ServerError::internal().with_msg(e)) hash(plain, hashing_cost).map_err(|e| ServerError::internal().context(e))
} }
pub fn verify_password(source: &str, hash: &str) -> Result<bool, ServerError> { pub fn verify_password(source: &str, hash: &str) -> Result<bool, ServerError> {
match verify(source, hash) { match verify(source, hash) {
Ok(true) => Ok(true), Ok(true) => Ok(true),
_ => Err(ServerError { _ => Err(ServerError {
code: Code::PasswordNotMatch, code: ErrorCode::PasswordNotMatch,
msg: "Username and password don't match".to_string(), msg: "Username and password don't match".to_string(),
}), }),
} }

View file

@ -4,4 +4,5 @@ pub const HOST: &'static str = "http://localhost:8000";
lazy_static! { lazy_static! {
pub static ref SIGN_UP_URL: String = format!("{}/api/register", HOST); pub static ref SIGN_UP_URL: String = format!("{}/api/register", HOST);
pub static ref SIGN_IN_URL: String = format!("{}/api/auth", HOST);
} }

View file

@ -7,7 +7,7 @@ use crate::response::FlowyResponse;
#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone)] #[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone)]
pub struct ServerError { pub struct ServerError {
pub code: Code, pub code: ErrorCode,
pub msg: String, pub msg: String,
} }
@ -24,13 +24,14 @@ macro_rules! static_error {
} }
impl ServerError { impl ServerError {
static_error!(internal, Code::InternalError); static_error!(internal, ErrorCode::InternalError);
static_error!(http, Code::HttpError); static_error!(http, ErrorCode::HttpError);
static_error!(payload_none, Code::PayloadUnexpectedNone); static_error!(payload_none, ErrorCode::PayloadUnexpectedNone);
static_error!(unauthorized, Code::Unauthorized); static_error!(unauthorized, ErrorCode::Unauthorized);
static_error!(passwordNotMatch, Code::PasswordNotMatch); static_error!(password_not_match, ErrorCode::PasswordNotMatch);
static_error!(params_invalid, ErrorCode::ParamsInvalid);
pub fn with_msg<T: Debug>(mut self, error: T) -> Self { pub fn context<T: Debug>(mut self, error: T) -> Self {
self.msg = format!("{:?}", error); self.msg = format!("{:?}", error);
self self
} }
@ -54,7 +55,7 @@ impl std::convert::From<&ServerError> for FlowyResponse {
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug, Clone, derive_more::Display)] #[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug, Clone, derive_more::Display)]
#[repr(u16)] #[repr(u16)]
pub enum Code { pub enum ErrorCode {
#[display(fmt = "Token is invalid")] #[display(fmt = "Token is invalid")]
InvalidToken = 1, InvalidToken = 1,
#[display(fmt = "Unauthorized")] #[display(fmt = "Unauthorized")]
@ -65,6 +66,8 @@ pub enum Code {
PayloadSerdeFail = 4, PayloadSerdeFail = 4,
#[display(fmt = "Unexpected empty payload")] #[display(fmt = "Unexpected empty payload")]
PayloadUnexpectedNone = 5, PayloadUnexpectedNone = 5,
#[display(fmt = "Params is invalid")]
ParamsInvalid = 6,
#[display(fmt = "Protobuf serde error")] #[display(fmt = "Protobuf serde error")]
ProtobufError = 10, ProtobufError = 10,

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
errors::{Code, ServerError}, errors::{ErrorCode, ServerError},
response::FlowyResponse, response::FlowyResponse,
}; };
use bytes::Bytes; use bytes::Bytes;
@ -83,7 +83,7 @@ impl HttpRequestBuilder {
match data { match data {
None => { None => {
let msg = format!("Request: {} receives unexpected empty body", self.url); let msg = format!("Request: {} receives unexpected empty body", self.url);
Err(ServerError::payload_none().with_msg(msg)) Err(ServerError::payload_none().context(msg))
}, },
Some(data) => Ok(T2::try_from(data)?), Some(data) => Ok(T2::try_from(data)?),
} }
@ -121,7 +121,7 @@ async fn get_response_data(original: Response) -> Result<Bytes, ServerError> {
Some(error) => Err(error), Some(error) => Err(error),
} }
} else { } else {
Err(ServerError::http().with_msg(original)) Err(ServerError::http().context(original))
} }
} }

View file

@ -1,4 +1,4 @@
use crate::errors::{Code, ServerError}; use crate::errors::{ErrorCode, ServerError};
use bytes::Bytes; use bytes::Bytes;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{convert::TryInto, error::Error, fmt::Debug}; use std::{convert::TryInto, error::Error, fmt::Debug};
@ -25,35 +25,35 @@ impl FlowyResponse {
impl std::convert::From<protobuf::ProtobufError> for ServerError { impl std::convert::From<protobuf::ProtobufError> for ServerError {
fn from(err: protobuf::ProtobufError) -> Self { fn from(err: protobuf::ProtobufError) -> Self {
ServerError { ServerError {
code: Code::ProtobufError, code: ErrorCode::ProtobufError,
msg: format!("{}", err), msg: format!("{}", err),
} }
} }
} }
impl std::convert::From<RecvError> for ServerError { impl std::convert::From<RecvError> for ServerError {
fn from(error: RecvError) -> Self { ServerError::internal().with_msg(error) } fn from(error: RecvError) -> Self { ServerError::internal().context(error) }
} }
impl std::convert::From<serde_json::Error> for ServerError { impl std::convert::From<serde_json::Error> for ServerError {
fn from(e: serde_json::Error) -> Self { fn from(e: serde_json::Error) -> Self {
let msg = format!("Serial error: {:?}", e); let msg = format!("Serial error: {:?}", e);
ServerError { ServerError {
code: Code::SerdeError, code: ErrorCode::SerdeError,
msg, msg,
} }
} }
} }
impl std::convert::From<anyhow::Error> for ServerError { impl std::convert::From<anyhow::Error> for ServerError {
fn from(error: anyhow::Error) -> Self { ServerError::internal().with_msg(error) } fn from(error: anyhow::Error) -> Self { ServerError::internal().context(error) }
} }
impl std::convert::From<reqwest::Error> for ServerError { impl std::convert::From<reqwest::Error> for ServerError {
fn from(error: reqwest::Error) -> Self { fn from(error: reqwest::Error) -> Self {
if error.is_timeout() { if error.is_timeout() {
return ServerError { return ServerError {
code: Code::ConnectTimeout, code: ErrorCode::ConnectTimeout,
msg: format!("{}", error), msg: format!("{}", error),
}; };
} }
@ -62,22 +62,22 @@ impl std::convert::From<reqwest::Error> for ServerError {
let hyper_error: Option<&hyper::Error> = error.source().unwrap().downcast_ref(); let hyper_error: Option<&hyper::Error> = error.source().unwrap().downcast_ref();
return match hyper_error { return match hyper_error {
None => ServerError { None => ServerError {
code: Code::ConnectRefused, code: ErrorCode::ConnectRefused,
msg: format!("{:?}", error), msg: format!("{:?}", error),
}, },
Some(hyper_error) => { Some(hyper_error) => {
let mut code = Code::InternalError; let mut code = ErrorCode::InternalError;
let msg = format!("{}", error); let msg = format!("{}", error);
if hyper_error.is_closed() { if hyper_error.is_closed() {
code = Code::ConnectClose; code = ErrorCode::ConnectClose;
} }
if hyper_error.is_connect() { if hyper_error.is_connect() {
code = Code::ConnectRefused; code = ErrorCode::ConnectRefused;
} }
if hyper_error.is_canceled() { if hyper_error.is_canceled() {
code = Code::ConnectCancel; code = ErrorCode::ConnectCancel;
} }
if hyper_error.is_timeout() {} if hyper_error.is_timeout() {}
@ -89,7 +89,7 @@ impl std::convert::From<reqwest::Error> for ServerError {
let msg = format!("{:?}", error); let msg = format!("{:?}", error);
ServerError { ServerError {
code: Code::ProtobufError, code: ErrorCode::ProtobufError,
msg, msg,
} }
} }

View file

@ -2,7 +2,7 @@ pub use sign_in::*;
pub use sign_up::*; pub use sign_up::*;
pub use user_detail::*; pub use user_detail::*;
pub use user_update::*; pub use user_update::*;
mod parser; pub mod parser;
mod sign_in; mod sign_in;
pub mod sign_up; pub mod sign_up;
mod user_detail; mod user_detail;

View file

@ -63,6 +63,7 @@ pub enum UserErrCode {
fmt = "Password should contain a minimum of 6 characters with 1 special 1 letter and 1 numeric" fmt = "Password should contain a minimum of 6 characters with 1 special 1 letter and 1 numeric"
)] )]
PasswordFormatInvalid = 33, PasswordFormatInvalid = 33,
#[display(fmt = "User name is too long")] #[display(fmt = "User name is too long")]
UserNameTooLong = 40, UserNameTooLong = 40,
#[display(fmt = "User name contain forbidden characters")] #[display(fmt = "User name contain forbidden characters")]
@ -83,6 +84,10 @@ pub enum UserErrCode {
NetworkError = 100, NetworkError = 100,
} }
impl UserErrCode {
pub fn to_string(&self) -> String { format!("{}", self) }
}
impl std::default::Default for UserErrCode { impl std::default::Default for UserErrCode {
fn default() -> Self { UserErrCode::Unknown } fn default() -> Self { UserErrCode::Unknown }
} }

View file

@ -4,7 +4,7 @@ use crate::{
}; };
use flowy_net::{ use flowy_net::{
config::SIGN_UP_URL, config::*,
future::ResultFuture, future::ResultFuture,
request::{http_post, HttpRequestBuilder}, request::{http_post, HttpRequestBuilder},
}; };
@ -35,15 +35,8 @@ impl UserServer for UserServerImpl {
ResultFuture::new(async move { user_sign_up(params, SIGN_UP_URL.as_ref()).await }) ResultFuture::new(async move { user_sign_up(params, SIGN_UP_URL.as_ref()).await })
} }
fn sign_in(&self, _params: SignInParams) -> ResultFuture<SignInResponse, UserError> { fn sign_in(&self, params: SignInParams) -> ResultFuture<SignInResponse, UserError> {
// let user_id = params.email.clone(); ResultFuture::new(async move { user_sign_in(params, SIGN_IN_URL.as_ref()).await })
// Ok(UserTable::new(
// user_id,
// "".to_owned(),
// params.email,
// params.password,
// ))
unimplemented!()
} }
fn sign_out(&self, _user_id: &str) -> ResultFuture<(), UserError> { fn sign_out(&self, _user_id: &str) -> ResultFuture<(), UserError> {