From 15f126795624563c49db3badb348d74df2301d42 Mon Sep 17 00:00:00 2001 From: appflowy Date: Mon, 23 Aug 2021 18:39:10 +0800 Subject: [PATCH] save user password with bcrypt --- .../lib/protobuf/flowy-user/sign_in.pb.dart | 14 ++ .../protobuf/flowy-user/sign_in.pbjson.dart | 3 +- backend/Cargo.toml | 5 +- backend/src/application.rs | 40 +++++- backend/src/config/env.rs | 9 ++ backend/src/config/mod.rs | 1 + backend/src/entities/mod.rs | 3 +- backend/src/entities/token.rs | 66 +++++++++ backend/src/entities/user.rs | 8 ++ backend/src/routers/mod.rs | 10 +- backend/src/routers/user.rs | 25 ---- backend/src/routers/user_router.rs | 57 ++++++++ backend/src/routers/{helper.rs => utils.rs} | 7 +- backend/src/routers/{ws.rs => ws_router.rs} | 0 backend/src/user_service/auth.rs | 73 ---------- backend/src/user_service/auth_service.rs | 132 ++++++++++++++++++ backend/src/user_service/mod.rs | 7 +- backend/src/user_service/utils.rs | 24 ++++ .../src/derive_cache/derive_cache.rs | 1 - rust-lib/flowy-net/src/config.rs | 2 +- rust-lib/flowy-net/src/errors.rs | 44 ++++-- rust-lib/flowy-net/src/request/request.rs | 16 +-- rust-lib/flowy-net/src/response/response.rs | 4 +- rust-lib/flowy-user/src/entities/sign_in.rs | 3 + .../flowy-user/src/protobuf/model/sign_in.rs | 98 +++++++++---- .../src/protobuf/proto/sign_in.proto | 1 + .../src/services/user/user_server.rs | 1 + 27 files changed, 491 insertions(+), 163 deletions(-) create mode 100644 backend/src/config/env.rs create mode 100644 backend/src/entities/token.rs create mode 100644 backend/src/entities/user.rs delete mode 100644 backend/src/routers/user.rs create mode 100644 backend/src/routers/user_router.rs rename backend/src/routers/{helper.rs => utils.rs} (86%) rename backend/src/routers/{ws.rs => ws_router.rs} (100%) delete mode 100644 backend/src/user_service/auth.rs create mode 100644 backend/src/user_service/auth_service.rs create mode 100644 backend/src/user_service/utils.rs diff --git a/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/sign_in.pb.dart b/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/sign_in.pb.dart index 4074448ca4..af4a85cee8 100644 --- a/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/sign_in.pb.dart +++ b/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/sign_in.pb.dart @@ -136,6 +136,7 @@ class SignInResponse extends $pb.GeneratedMessage { ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'uid') ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name') ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'email') + ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'token') ..hasRequiredFields = false ; @@ -144,6 +145,7 @@ class SignInResponse extends $pb.GeneratedMessage { $core.String? uid, $core.String? name, $core.String? email, + $core.String? token, }) { final _result = create(); if (uid != null) { @@ -155,6 +157,9 @@ class SignInResponse extends $pb.GeneratedMessage { if (email != null) { _result.email = email; } + if (token != null) { + _result.token = token; + } return _result; } factory SignInResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); @@ -204,5 +209,14 @@ class SignInResponse extends $pb.GeneratedMessage { $core.bool hasEmail() => $_has(2); @$pb.TagNumber(3) void clearEmail() => clearField(3); + + @$pb.TagNumber(4) + $core.String get token => $_getSZ(3); + @$pb.TagNumber(4) + set token($core.String v) { $_setString(3, v); } + @$pb.TagNumber(4) + $core.bool hasToken() => $_has(3); + @$pb.TagNumber(4) + void clearToken() => clearField(4); } diff --git a/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/sign_in.pbjson.dart b/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/sign_in.pbjson.dart index 77787ec279..fff8561b23 100644 --- a/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/sign_in.pbjson.dart +++ b/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/sign_in.pbjson.dart @@ -37,8 +37,9 @@ const SignInResponse$json = const { const {'1': 'uid', '3': 1, '4': 1, '5': 9, '10': 'uid'}, const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'}, const {'1': 'email', '3': 3, '4': 1, '5': 9, '10': 'email'}, + const {'1': 'token', '3': 4, '4': 1, '5': 9, '10': 'token'}, ], }; /// Descriptor for `SignInResponse`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List signInResponseDescriptor = $convert.base64Decode('Cg5TaWduSW5SZXNwb25zZRIQCgN1aWQYASABKAlSA3VpZBISCgRuYW1lGAIgASgJUgRuYW1lEhQKBWVtYWlsGAMgASgJUgVlbWFpbA=='); +final $typed_data.Uint8List signInResponseDescriptor = $convert.base64Decode('Cg5TaWduSW5SZXNwb25zZRIQCgN1aWQYASABKAlSA3VpZBISCgRuYW1lGAIgASgJUgRuYW1lEhQKBWVtYWlsGAMgASgJUgVlbWFpbBIUCgV0b2tlbhgEIAEoCVIFdG9rZW4='); diff --git a/backend/Cargo.toml b/backend/Cargo.toml index d1a37b0dec..0ed25e040a 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -14,6 +14,7 @@ actix-codec = "0.3" actix-web = "4.0.0-beta.8" actix-http = "3.0.0-beta.8" actix-web-actors = { version = "4.0.0-beta.6" } +actix-identity = "0.4.0-beta.2" futures = "0.3.15" bytes = "1" @@ -24,13 +25,15 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } serde_repr = "0.1" serde-aux = "1.0.1" -derive_more = {version = "0.99", features = ["display"]} +derive_more = {version = "0.99"} protobuf = {version = "2.20.0"} uuid = { version = "0.8", features = ["serde", "v4"] } config = { version = "0.10.1", default-features = false, features = ["yaml"] } chrono = "0.4.19" anyhow = "1.0.40" thiserror = "1.0.24" +bcrypt = "0.10" +jsonwebtoken = "7.2" flowy-log = { path = "../rust-lib/flowy-log" } flowy-user = { path = "../rust-lib/flowy-user" } diff --git a/backend/src/application.rs b/backend/src/application.rs index 455c6acdb3..91c9a96204 100644 --- a/backend/src/application.rs +++ b/backend/src/application.rs @@ -1,10 +1,16 @@ use crate::{ - config::{get_configuration, DatabaseSettings, Settings}, + config::{ + env::{domain, secret, use_https}, + get_configuration, + DatabaseSettings, + Settings, + }, context::AppContext, routers::*, ws_service::WSServer, }; use actix::Actor; +use actix_identity::{CookieIdentityPolicy, IdentityService}; use actix_web::{dev::Server, middleware, web, web::Data, App, HttpServer, Scope}; use sqlx::{postgres::PgPoolOptions, PgPool}; use std::{net::TcpListener, sync::Arc}; @@ -34,10 +40,13 @@ pub fn run(listener: TcpListener, app_ctx: AppContext) -> Result Result Scope { web::scope("/ws").service(ws::start_connection) } +fn ws_scope() -> Scope { web::scope("/ws").service(ws_router::start_connection) } fn user_scope() -> Scope { - web::scope("/user").service(web::resource("/register").route(web::post().to(user::register))) + web::scope("/api") + // authentication + .service(web::resource("/auth") + .route(web::post().to(user_router::login_handler)) + .route(web::delete().to(user_router::logout_handler)) + .route(web::get().to(user_router::user_profile)) + ) + // password + .service(web::resource("/password_change") + .route(web::post().to(user_router::change_password)) + ) + // register + .service(web::resource("/register") + .route(web::post().to(user_router::register_handler)) + ) } async fn init_app_context(configuration: &Settings) -> AppContext { @@ -69,6 +92,17 @@ async fn init_app_context(configuration: &Settings) -> AppContext { AppContext::new(ws_server, pg_pool) } +pub fn identify_service(domain: &str, secret: &str) -> IdentityService { + IdentityService::new( + CookieIdentityPolicy::new(secret.as_bytes()) + .name("auth") + .path("/") + .domain(domain) + .max_age_secs(24 * 3600) + .secure(use_https()), + ) +} + pub async fn get_connection_pool(configuration: &DatabaseSettings) -> Result { PgPoolOptions::new() .connect_timeout(std::time::Duration::from_secs(5)) diff --git a/backend/src/config/env.rs b/backend/src/config/env.rs new file mode 100644 index 0000000000..0cbd063341 --- /dev/null +++ b/backend/src/config/env.rs @@ -0,0 +1,9 @@ +use std::env; + +pub fn domain() -> String { env::var("DOMAIN").unwrap_or_else(|_| "localhost".to_string()) } + +pub fn jwt_secret() -> String { env::var("JWT_SECRET").unwrap_or_else(|_| "my secret".into()) } + +pub fn secret() -> String { env::var("SECRET_KEY").unwrap_or_else(|_| "0123".repeat(8)) } + +pub fn use_https() -> bool { false } diff --git a/backend/src/config/mod.rs b/backend/src/config/mod.rs index d4610fe72f..af27ede86d 100644 --- a/backend/src/config/mod.rs +++ b/backend/src/config/mod.rs @@ -1,5 +1,6 @@ mod configuration; mod const_define; +pub mod env; pub use configuration::*; pub use const_define::*; diff --git a/backend/src/entities/mod.rs b/backend/src/entities/mod.rs index 8b13789179..87a75950a7 100644 --- a/backend/src/entities/mod.rs +++ b/backend/src/entities/mod.rs @@ -1 +1,2 @@ - +pub mod token; +pub mod user; diff --git a/backend/src/entities/token.rs b/backend/src/entities/token.rs new file mode 100644 index 0000000000..1c5411d4ec --- /dev/null +++ b/backend/src/entities/token.rs @@ -0,0 +1,66 @@ +use crate::{ + config::env::{domain, jwt_secret}, + entities::user::User, +}; +use chrono::{Duration, Local}; +use derive_more::{From, Into}; +use flowy_net::errors::{Code, ServerError}; +use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; +use serde::{Deserialize, Serialize}; + +const DEFAULT_ALGORITHM: Algorithm = Algorithm::HS256; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Claim { + // issuer + iss: String, + // subject + sub: String, + // issue at + iat: i64, + // expiry + exp: i64, + email: String, +} + +impl Claim { + pub fn with_email(email: &str) -> Self { + let domain = domain(); + Self { + iss: domain, + sub: "auth".to_string(), + email: email.to_string(), + iat: Local::now().timestamp(), + exp: (Local::now() + Duration::hours(24)).timestamp(), + } + } +} + +// impl From for User { +// fn from(claim: Claim) -> Self { Self { email: claim.email } } +// } + +#[derive(From, Into)] +pub struct Token(String); +impl Token { + pub fn create_token(data: &User) -> Result { + let claims = Claim::with_email(&data.email); + encode( + &Header::new(DEFAULT_ALGORITHM), + &claims, + &EncodingKey::from_secret(jwt_secret().as_ref()), + ) + .map(Into::into) + .map_err(|err| ServerError::internal().with_msg(err)) + } + + pub fn decode_token(token: &Self) -> Result { + decode::( + &token.0, + &DecodingKey::from_secret(jwt_secret().as_ref()), + &Validation::new(DEFAULT_ALGORITHM), + ) + .map(|data| Ok(data.claims)) + .map_err(|err| ServerError::unauthorized().with_msg(err))? + } +} diff --git a/backend/src/entities/user.rs b/backend/src/entities/user.rs new file mode 100644 index 0000000000..5abcfc9ef7 --- /dev/null +++ b/backend/src/entities/user.rs @@ -0,0 +1,8 @@ +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct User { + pub(crate) id: uuid::Uuid, + pub(crate) email: String, + pub(crate) name: String, + pub(crate) create_time: i64, + pub(crate) password: String, +} diff --git a/backend/src/routers/mod.rs b/backend/src/routers/mod.rs index 21700ba3ce..2f713b5f44 100644 --- a/backend/src/routers/mod.rs +++ b/backend/src/routers/mod.rs @@ -1,6 +1,6 @@ -mod helper; -pub(crate) mod user; -pub(crate) mod ws; +pub(crate) mod user_router; +mod utils; +pub(crate) mod ws_router; -pub use user::*; -pub use ws::*; +pub use user_router::*; +pub use ws_router::*; diff --git a/backend/src/routers/user.rs b/backend/src/routers/user.rs deleted file mode 100644 index 4d5f8cf942..0000000000 --- a/backend/src/routers/user.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::routers::helper::parse_from_payload; -use actix_web::{ - web::{Data, Payload}, - Error, - HttpRequest, - HttpResponse, -}; -use flowy_net::response::*; -use flowy_user::protobuf::SignUpParams; - -use crate::user_service::sign_up; -use flowy_net::errors::ServerError; -use sqlx::PgPool; -use std::sync::Arc; - -pub async fn register( - _request: HttpRequest, - payload: Payload, - pool: Data, -) -> Result { - let params: SignUpParams = parse_from_payload(payload).await?; - let resp = sign_up(pool.get_ref(), params).await?; - - Ok(resp.into()) -} diff --git a/backend/src/routers/user_router.rs b/backend/src/routers/user_router.rs new file mode 100644 index 0000000000..563141f273 --- /dev/null +++ b/backend/src/routers/user_router.rs @@ -0,0 +1,57 @@ +use crate::routers::utils::parse_from_payload; +use actix_web::{ + web::{Data, Payload}, + Error, + HttpRequest, + HttpResponse, +}; +use flowy_net::response::*; +use flowy_user::protobuf::{SignInParams, SignUpParams}; + +use crate::user_service::auth_service::{register_user, sign_in}; +use actix_identity::Identity; +use flowy_net::errors::ServerError; +use sqlx::PgPool; +use std::sync::Arc; + +pub async fn login_handler( + payload: Payload, + id: Identity, + pool: Data, +) -> Result { + let params: SignInParams = parse_from_payload(payload).await?; + let resp = sign_in(pool.get_ref(), params, id).await?; + Ok(resp.into()) +} + +pub async fn logout_handler(id: Identity) -> Result { + id.forget(); + Ok(HttpResponse::Ok().finish()) +} + +pub async fn user_profile( + request: HttpRequest, + payload: Payload, + pool: Data, +) -> Result { + unimplemented!() +} + +pub async fn register_handler( + _request: HttpRequest, + payload: Payload, + pool: Data, +) -> Result { + let params: SignUpParams = parse_from_payload(payload).await?; + let resp = register_user(pool.get_ref(), params).await?; + + Ok(resp.into()) +} + +pub async fn change_password( + request: HttpRequest, + payload: Payload, + pool: Data, +) -> Result { + unimplemented!() +} diff --git a/backend/src/routers/helper.rs b/backend/src/routers/utils.rs similarity index 86% rename from backend/src/routers/helper.rs rename to backend/src/routers/utils.rs index ba16bea1a4..945e2fa41d 100644 --- a/backend/src/routers/helper.rs +++ b/backend/src/routers/utils.rs @@ -1,6 +1,9 @@ use crate::config::MAX_PAYLOAD_SIZE; use actix_web::web; -use flowy_net::response::*; +use flowy_net::{ + errors::{Code, ServerError}, + response::*, +}; use futures::StreamExt; use protobuf::{Message, ProtobufResult}; @@ -20,7 +23,7 @@ pub fn parse_from_bytes(bytes: &[u8]) -> Result { pub async fn poll_payload(mut payload: web::Payload) -> Result { let mut body = web::BytesMut::new(); while let Some(chunk) = payload.next().await { - let chunk = chunk.map_err(ServerError::internal)?; + let chunk = chunk.map_err(|err| ServerError::internal().with_msg(err))?; if (body.len() + chunk.len()) > MAX_PAYLOAD_SIZE { return Err(ServerError { diff --git a/backend/src/routers/ws.rs b/backend/src/routers/ws_router.rs similarity index 100% rename from backend/src/routers/ws.rs rename to backend/src/routers/ws_router.rs diff --git a/backend/src/user_service/auth.rs b/backend/src/user_service/auth.rs deleted file mode 100644 index abeacafc03..0000000000 --- a/backend/src/user_service/auth.rs +++ /dev/null @@ -1,73 +0,0 @@ -use anyhow::Context; -use chrono::Utc; -use flowy_net::{errors::ServerError, response::FlowyResponse}; -use flowy_user::{entities::SignUpResponse, protobuf::SignUpParams}; -use sqlx::{Error, PgPool, Postgres, Transaction}; -use std::sync::Arc; - -pub async fn sign_up(pool: &PgPool, params: SignUpParams) -> Result { - let mut transaction = pool - .begin() - .await - .context("Failed to acquire a Postgres connection from the pool")?; - - let _ = is_email_exist(&mut transaction, ¶ms.email).await?; - - let data = insert_user(&mut transaction, params) - .await - .context("Failed to insert user")?; - - let response = FlowyResponse::success(data).context("Failed to generate response")?; - - Ok(response) -} - -async fn is_email_exist( - transaction: &mut Transaction<'_, Postgres>, - email: &str, -) -> Result<(), ServerError> { - let result = sqlx::query!( - r#"SELECT email FROM user_table WHERE email = $1"#, - email.to_string() - ) - .fetch_optional(transaction) - .await - .map_err(ServerError::internal)?; - - match result { - Some(_) => Err(ServerError { - code: Code::EmailAlreadyExists, - msg: format!("{} already exists", email), - }), - None => Ok(()), - } -} - -async fn insert_user( - transaction: &mut Transaction<'_, Postgres>, - params: SignUpParams, -) -> Result { - let uuid = uuid::Uuid::new_v4(); - let result = sqlx::query!( - r#" - INSERT INTO user_table (id, email, name, create_time, password) - VALUES ($1, $2, $3, $4, $5) - "#, - uuid, - params.email, - params.name, - Utc::now(), - "123".to_string() - ) - .execute(transaction) - .await - .map_err(ServerError::internal)?; - - let data = SignUpResponse { - uid: uuid.to_string(), - name: params.name, - email: params.email, - }; - - Ok(data) -} diff --git a/backend/src/user_service/auth_service.rs b/backend/src/user_service/auth_service.rs new file mode 100644 index 0000000000..7212c0354a --- /dev/null +++ b/backend/src/user_service/auth_service.rs @@ -0,0 +1,132 @@ +use crate::{ + entities::{token::Token, user::User}, + user_service::utils::{hash_password, verify_password}, +}; +use actix_identity::Identity; +use anyhow::Context; +use chrono::Utc; +use flowy_net::{ + errors::{Code, ServerError}, + response::FlowyResponse, +}; +use flowy_user::{ + entities::{SignInResponse, SignUpResponse}, + protobuf::{SignInParams, SignUpParams}, +}; +use sqlx::{Error, PgPool, Postgres, Transaction}; +use std::sync::Arc; + +pub async fn sign_in( + pool: &PgPool, + params: SignInParams, + id: Identity, +) -> Result { + let mut transaction = pool + .begin() + .await + .context("Failed to acquire a Postgres connection to sign in")?; + let user = read_user(&mut transaction, ¶ms.email).await?; + transaction + .commit() + .await + .context("Failed to commit SQL transaction to sign in.")?; + + match verify_password(¶ms.password, &user.password) { + Ok(true) => { + let token = Token::create_token(&user)?; + let data = SignInResponse { + uid: user.id.to_string(), + name: user.name, + email: user.email, + token: token.into(), + }; + id.remember(data.token.clone()); + FlowyResponse::success(data) + }, + _ => Err(ServerError::passwordNotMatch()), + } +} + +pub async fn register_user( + pool: &PgPool, + params: SignUpParams, +) -> Result { + let mut transaction = pool + .begin() + .await + .context("Failed to acquire a Postgres connection to register user")?; + + let _ = is_email_exist(&mut transaction, ¶ms.email).await?; + let data = insert_user(&mut transaction, params) + .await + .context("Failed to insert user")?; + + transaction + .commit() + .await + .context("Failed to commit SQL transaction to register user.")?; + + FlowyResponse::success(data) +} + +async fn is_email_exist( + transaction: &mut Transaction<'_, Postgres>, + email: &str, +) -> Result<(), ServerError> { + let result = sqlx::query(r#"SELECT email FROM user_table WHERE email = $1"#) + .bind(email) + .fetch_optional(transaction) + .await + .map_err(|err| ServerError::internal().with_msg(err))?; + + match result { + Some(_) => Err(ServerError { + code: Code::EmailAlreadyExists, + msg: format!("{} already exists", email), + }), + None => Ok(()), + } +} + +async fn read_user( + transaction: &mut Transaction<'_, Postgres>, + email: &str, +) -> Result { + let user = sqlx::query_as::("SELECT * FROM user_table WHERE email = $1") + .bind(email) + .fetch_one(transaction) + .await + .map_err(|err| ServerError::internal().with_msg(err))?; + + Ok(user) +} + +async fn insert_user( + transaction: &mut Transaction<'_, Postgres>, + params: SignUpParams, +) -> Result { + let uuid = uuid::Uuid::new_v4(); + let password = hash_password(¶ms.password)?; + let _ = sqlx::query!( + r#" + INSERT INTO user_table (id, email, name, create_time, password) + VALUES ($1, $2, $3, $4, $5) + "#, + uuid, + params.email, + params.name, + Utc::now(), + "123".to_string() + ) + .execute(transaction) + .await + .map_err(|e| ServerError::internal().with_msg(e))?; + + let data = SignUpResponse { + uid: uuid.to_string(), + name: params.name, + email: params.email, + }; + + Ok(data) +} diff --git a/backend/src/user_service/mod.rs b/backend/src/user_service/mod.rs index 2a3e687764..430bd42166 100644 --- a/backend/src/user_service/mod.rs +++ b/backend/src/user_service/mod.rs @@ -1,5 +1,2 @@ -mod auth; - -pub use auth::*; - -pub fn uuid() -> String { uuid::Uuid::new_v4().to_string() } +pub mod auth_service; +pub mod utils; diff --git a/backend/src/user_service/utils.rs b/backend/src/user_service/utils.rs new file mode 100644 index 0000000000..485963ae58 --- /dev/null +++ b/backend/src/user_service/utils.rs @@ -0,0 +1,24 @@ +use bcrypt::{hash, verify, BcryptError, DEFAULT_COST}; +use flowy_net::errors::{Code, ServerError}; +use jsonwebtoken::Algorithm; + +pub fn uuid() -> String { uuid::Uuid::new_v4().to_string() } + +pub fn hash_password(plain: &str) -> Result { + let hashing_cost = std::env::var("HASH_COST") + .ok() + .and_then(|c| c.parse().ok()) + .unwrap_or(DEFAULT_COST); + + hash(plain, hashing_cost).map_err(|e| ServerError::internal().with_msg(e)) +} + +pub fn verify_password(source: &str, hash: &str) -> Result { + match verify(source, hash) { + Ok(true) => Ok(true), + _ => Err(ServerError { + code: Code::PasswordNotMatch, + msg: "Username and password don't match".to_string(), + }), + } +} diff --git a/rust-lib/flowy-derive/src/derive_cache/derive_cache.rs b/rust-lib/flowy-derive/src/derive_cache/derive_cache.rs index 6bcea530cb..d0ebd87160 100644 --- a/rust-lib/flowy-derive/src/derive_cache/derive_cache.rs +++ b/rust-lib/flowy-derive/src/derive_cache/derive_cache.rs @@ -64,7 +64,6 @@ pub fn category_from_str(type_str: &str) -> TypeCategory { | "EditorEvent" | "DocErrorCode" | "FFIStatusCode" - | "UserServerError" | "UserStatus" | "UserEvent" | "UserErrCode" diff --git a/rust-lib/flowy-net/src/config.rs b/rust-lib/flowy-net/src/config.rs index 51aef47acc..70afa0ca34 100644 --- a/rust-lib/flowy-net/src/config.rs +++ b/rust-lib/flowy-net/src/config.rs @@ -3,5 +3,5 @@ use lazy_static::lazy_static; pub const HOST: &'static str = "http://localhost:8000"; lazy_static! { - pub static ref SIGN_UP_URL: String = format!("{}/user/register", HOST); + pub static ref SIGN_UP_URL: String = format!("{}/api/register", HOST); } diff --git a/rust-lib/flowy-net/src/errors.rs b/rust-lib/flowy-net/src/errors.rs index 75849c287f..383d6c914a 100644 --- a/rust-lib/flowy-net/src/errors.rs +++ b/rust-lib/flowy-net/src/errors.rs @@ -14,9 +14,11 @@ pub struct ServerError { macro_rules! static_error { ($name:ident, $status:expr) => { #[allow(non_snake_case, missing_docs)] - pub fn $name(error: T) -> ServerError { - let msg = format!("{:?}", error); - ServerError { code: $status, msg } + pub fn $name() -> ServerError { + ServerError { + code: $status, + msg: format!("{}", $status), + } } }; } @@ -25,6 +27,13 @@ impl ServerError { static_error!(internal, Code::InternalError); static_error!(http, Code::HttpError); static_error!(payload_none, Code::PayloadUnexpectedNone); + static_error!(unauthorized, Code::Unauthorized); + static_error!(passwordNotMatch, Code::PasswordNotMatch); + + pub fn with_msg(mut self, error: T) -> Self { + self.msg = format!("{:?}", error); + self + } } impl std::fmt::Display for ServerError { @@ -43,28 +52,47 @@ impl std::convert::From<&ServerError> for FlowyResponse { } } -#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug, Clone)] +#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug, Clone, derive_more::Display)] #[repr(u16)] pub enum Code { + #[display(fmt = "Token is invalid")] InvalidToken = 1, - Unauthorized = 3, - PayloadOverflow = 4, - PayloadSerdeFail = 5, - PayloadUnexpectedNone = 6, + #[display(fmt = "Unauthorized")] + Unauthorized = 2, + #[display(fmt = "Payload too large")] + PayloadOverflow = 3, + #[display(fmt = "Payload deserialize failed")] + PayloadSerdeFail = 4, + #[display(fmt = "Unexpected empty payload")] + PayloadUnexpectedNone = 5, + #[display(fmt = "Protobuf serde error")] ProtobufError = 10, + #[display(fmt = "Json serde Error")] SerdeError = 11, + #[display(fmt = "Email address already exists")] EmailAlreadyExists = 50, + #[display(fmt = "Username and password do not match")] + PasswordNotMatch = 51, + + #[display(fmt = "Connect refused")] ConnectRefused = 100, + + #[display(fmt = "Connection timeout")] ConnectTimeout = 101, + #[display(fmt = "Connection closed")] ConnectClose = 102, + #[display(fmt = "Connection canceled")] ConnectCancel = 103, + #[display(fmt = "Sql error")] SqlError = 200, + #[display(fmt = "Http request error")] HttpError = 300, + #[display(fmt = "Internal error")] InternalError = 1000, } diff --git a/rust-lib/flowy-net/src/request/request.rs b/rust-lib/flowy-net/src/request/request.rs index da8c222ff3..42e4bfd513 100644 --- a/rust-lib/flowy-net/src/request/request.rs +++ b/rust-lib/flowy-net/src/request/request.rs @@ -20,9 +20,9 @@ pub struct HttpRequestBuilder { } impl HttpRequestBuilder { - fn new() -> Self { + fn new(url: &str) -> Self { Self { - url: "".to_owned(), + url: url.to_owned(), body: None, response: None, method: Method::GET, @@ -30,15 +30,13 @@ impl HttpRequestBuilder { } pub fn get(url: &str) -> Self { - let mut builder = Self::new(); - builder.url = url.to_owned(); + let mut builder = Self::new(url); builder.method = Method::GET; builder } pub fn post(url: &str) -> Self { - let mut builder = Self::new(); - builder.url = url.to_owned(); + let mut builder = Self::new(url); builder.method = Method::POST; builder } @@ -54,11 +52,11 @@ impl HttpRequestBuilder { pub async fn send(mut self) -> Result { let (tx, rx) = oneshot::channel::>(); - // reqwest client is not 'Sync' by channel is. let url = self.url.clone(); let body = self.body.take(); let method = self.method.clone(); + // reqwest client is not 'Sync' by channel is. tokio::spawn(async move { let client = default_client(); let mut builder = client.request(method, url); @@ -85,7 +83,7 @@ impl HttpRequestBuilder { match data { None => { let msg = format!("Request: {} receives unexpected empty body", self.url); - Err(ServerError::payload_none(msg)) + Err(ServerError::payload_none().with_msg(msg)) }, Some(data) => Ok(T2::try_from(data)?), } @@ -123,7 +121,7 @@ async fn get_response_data(original: Response) -> Result { Some(error) => Err(error), } } else { - Err(ServerError::http(original)) + Err(ServerError::http().with_msg(original)) } } diff --git a/rust-lib/flowy-net/src/response/response.rs b/rust-lib/flowy-net/src/response/response.rs index b78b0ac508..b2f4b321dc 100644 --- a/rust-lib/flowy-net/src/response/response.rs +++ b/rust-lib/flowy-net/src/response/response.rs @@ -32,7 +32,7 @@ impl std::convert::From for ServerError { } impl std::convert::From for ServerError { - fn from(error: RecvError) -> Self { ServerError::internal(error) } + fn from(error: RecvError) -> Self { ServerError::internal().with_msg(error) } } impl std::convert::From for ServerError { @@ -46,7 +46,7 @@ impl std::convert::From for ServerError { } impl std::convert::From for ServerError { - fn from(error: anyhow::Error) -> Self { ServerError::internal(error) } + fn from(error: anyhow::Error) -> Self { ServerError::internal().with_msg(error) } } impl std::convert::From for ServerError { diff --git a/rust-lib/flowy-user/src/entities/sign_in.rs b/rust-lib/flowy-user/src/entities/sign_in.rs index a2c75f282d..005dacbe37 100644 --- a/rust-lib/flowy-user/src/entities/sign_in.rs +++ b/rust-lib/flowy-user/src/entities/sign_in.rs @@ -30,6 +30,9 @@ pub struct SignInResponse { #[pb(index = 3)] pub email: String, + + #[pb(index = 4)] + pub token: String, } impl TryInto for SignInRequest { diff --git a/rust-lib/flowy-user/src/protobuf/model/sign_in.rs b/rust-lib/flowy-user/src/protobuf/model/sign_in.rs index f251f1b427..b8df7c94da 100644 --- a/rust-lib/flowy-user/src/protobuf/model/sign_in.rs +++ b/rust-lib/flowy-user/src/protobuf/model/sign_in.rs @@ -431,6 +431,7 @@ pub struct SignInResponse { pub uid: ::std::string::String, pub name: ::std::string::String, pub email: ::std::string::String, + pub token: ::std::string::String, // special fields pub unknown_fields: ::protobuf::UnknownFields, pub cached_size: ::protobuf::CachedSize, @@ -524,6 +525,32 @@ impl SignInResponse { pub fn take_email(&mut self) -> ::std::string::String { ::std::mem::replace(&mut self.email, ::std::string::String::new()) } + + // string token = 4; + + + pub fn get_token(&self) -> &str { + &self.token + } + pub fn clear_token(&mut self) { + self.token.clear(); + } + + // Param is passed by value, moved + pub fn set_token(&mut self, v: ::std::string::String) { + self.token = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_token(&mut self) -> &mut ::std::string::String { + &mut self.token + } + + // Take field + pub fn take_token(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.token, ::std::string::String::new()) + } } impl ::protobuf::Message for SignInResponse { @@ -544,6 +571,9 @@ impl ::protobuf::Message for SignInResponse { 3 => { ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.email)?; }, + 4 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.token)?; + }, _ => { ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; }, @@ -565,6 +595,9 @@ impl ::protobuf::Message for SignInResponse { if !self.email.is_empty() { my_size += ::protobuf::rt::string_size(3, &self.email); } + if !self.token.is_empty() { + my_size += ::protobuf::rt::string_size(4, &self.token); + } my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); self.cached_size.set(my_size); my_size @@ -580,6 +613,9 @@ impl ::protobuf::Message for SignInResponse { if !self.email.is_empty() { os.write_string(3, &self.email)?; } + if !self.token.is_empty() { + os.write_string(4, &self.token)?; + } os.write_unknown_fields(self.get_unknown_fields())?; ::std::result::Result::Ok(()) } @@ -633,6 +669,11 @@ impl ::protobuf::Message for SignInResponse { |m: &SignInResponse| { &m.email }, |m: &mut SignInResponse| { &mut m.email }, )); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "token", + |m: &SignInResponse| { &m.token }, + |m: &mut SignInResponse| { &mut m.token }, + )); ::protobuf::reflect::MessageDescriptor::new_pb_name::( "SignInResponse", fields, @@ -652,6 +693,7 @@ impl ::protobuf::Clear for SignInResponse { self.uid.clear(); self.name.clear(); self.email.clear(); + self.token.clear(); self.unknown_fields.clear(); } } @@ -672,33 +714,37 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \n\rsign_in.proto\"A\n\rSignInRequest\x12\x14\n\x05email\x18\x01\x20\x01\ (\tR\x05email\x12\x1a\n\x08password\x18\x02\x20\x01(\tR\x08password\"@\n\ \x0cSignInParams\x12\x14\n\x05email\x18\x01\x20\x01(\tR\x05email\x12\x1a\ - \n\x08password\x18\x02\x20\x01(\tR\x08password\"L\n\x0eSignInResponse\ + \n\x08password\x18\x02\x20\x01(\tR\x08password\"b\n\x0eSignInResponse\ \x12\x10\n\x03uid\x18\x01\x20\x01(\tR\x03uid\x12\x12\n\x04name\x18\x02\ - \x20\x01(\tR\x04name\x12\x14\n\x05email\x18\x03\x20\x01(\tR\x05emailJ\ - \xdb\x03\n\x06\x12\x04\0\0\x0e\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\ - \x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x15\ - \n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x15\n\x0c\n\x05\x04\0\x02\0\x05\ - \x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\x10\n\x0c\ - \n\x05\x04\0\x02\0\x03\x12\x03\x03\x13\x14\n\x0b\n\x04\x04\0\x02\x01\x12\ - \x03\x04\x04\x18\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\ - \x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\x13\n\x0c\n\x05\x04\0\x02\x01\x03\ - \x12\x03\x04\x16\x17\n\n\n\x02\x04\x01\x12\x04\x06\0\t\x01\n\n\n\x03\x04\ - \x01\x01\x12\x03\x06\x08\x14\n\x0b\n\x04\x04\x01\x02\0\x12\x03\x07\x04\ - \x15\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x07\x04\n\n\x0c\n\x05\x04\x01\ - \x02\0\x01\x12\x03\x07\x0b\x10\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\x07\ - \x13\x14\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\x08\x04\x18\n\x0c\n\x05\x04\ - \x01\x02\x01\x05\x12\x03\x08\x04\n\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\ - \x03\x08\x0b\x13\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\x08\x16\x17\n\n\ - \n\x02\x04\x02\x12\x04\n\0\x0e\x01\n\n\n\x03\x04\x02\x01\x12\x03\n\x08\ - \x16\n\x0b\n\x04\x04\x02\x02\0\x12\x03\x0b\x04\x13\n\x0c\n\x05\x04\x02\ - \x02\0\x05\x12\x03\x0b\x04\n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\x0b\ - \x0b\x0e\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x0b\x11\x12\n\x0b\n\x04\ - \x04\x02\x02\x01\x12\x03\x0c\x04\x14\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\ - \x03\x0c\x04\n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\x0c\x0b\x0f\n\x0c\ - \n\x05\x04\x02\x02\x01\x03\x12\x03\x0c\x12\x13\n\x0b\n\x04\x04\x02\x02\ - \x02\x12\x03\r\x04\x15\n\x0c\n\x05\x04\x02\x02\x02\x05\x12\x03\r\x04\n\n\ - \x0c\n\x05\x04\x02\x02\x02\x01\x12\x03\r\x0b\x10\n\x0c\n\x05\x04\x02\x02\ - \x02\x03\x12\x03\r\x13\x14b\x06proto3\ + \x20\x01(\tR\x04name\x12\x14\n\x05email\x18\x03\x20\x01(\tR\x05email\x12\ + \x14\n\x05token\x18\x04\x20\x01(\tR\x05tokenJ\x92\x04\n\x06\x12\x04\0\0\ + \x0f\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\ + \x05\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x15\n\x0b\n\x04\x04\0\x02\0\ + \x12\x03\x03\x04\x15\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\ + \n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\x10\n\x0c\n\x05\x04\0\x02\0\x03\ + \x12\x03\x03\x13\x14\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x18\n\x0c\ + \n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\ + \x12\x03\x04\x0b\x13\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x16\x17\n\ + \n\n\x02\x04\x01\x12\x04\x06\0\t\x01\n\n\n\x03\x04\x01\x01\x12\x03\x06\ + \x08\x14\n\x0b\n\x04\x04\x01\x02\0\x12\x03\x07\x04\x15\n\x0c\n\x05\x04\ + \x01\x02\0\x05\x12\x03\x07\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\ + \x07\x0b\x10\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\x07\x13\x14\n\x0b\n\ + \x04\x04\x01\x02\x01\x12\x03\x08\x04\x18\n\x0c\n\x05\x04\x01\x02\x01\x05\ + \x12\x03\x08\x04\n\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\x08\x0b\x13\n\ + \x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\x08\x16\x17\n\n\n\x02\x04\x02\x12\ + \x04\n\0\x0f\x01\n\n\n\x03\x04\x02\x01\x12\x03\n\x08\x16\n\x0b\n\x04\x04\ + \x02\x02\0\x12\x03\x0b\x04\x13\n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\x0b\ + \x04\n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\x0b\x0b\x0e\n\x0c\n\x05\x04\ + \x02\x02\0\x03\x12\x03\x0b\x11\x12\n\x0b\n\x04\x04\x02\x02\x01\x12\x03\ + \x0c\x04\x14\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\x0c\x04\n\n\x0c\n\ + \x05\x04\x02\x02\x01\x01\x12\x03\x0c\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x01\ + \x03\x12\x03\x0c\x12\x13\n\x0b\n\x04\x04\x02\x02\x02\x12\x03\r\x04\x15\n\ + \x0c\n\x05\x04\x02\x02\x02\x05\x12\x03\r\x04\n\n\x0c\n\x05\x04\x02\x02\ + \x02\x01\x12\x03\r\x0b\x10\n\x0c\n\x05\x04\x02\x02\x02\x03\x12\x03\r\x13\ + \x14\n\x0b\n\x04\x04\x02\x02\x03\x12\x03\x0e\x04\x15\n\x0c\n\x05\x04\x02\ + \x02\x03\x05\x12\x03\x0e\x04\n\n\x0c\n\x05\x04\x02\x02\x03\x01\x12\x03\ + \x0e\x0b\x10\n\x0c\n\x05\x04\x02\x02\x03\x03\x12\x03\x0e\x13\x14b\x06pro\ + to3\ "; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; diff --git a/rust-lib/flowy-user/src/protobuf/proto/sign_in.proto b/rust-lib/flowy-user/src/protobuf/proto/sign_in.proto index c98d1ece53..230a4d50bd 100644 --- a/rust-lib/flowy-user/src/protobuf/proto/sign_in.proto +++ b/rust-lib/flowy-user/src/protobuf/proto/sign_in.proto @@ -12,4 +12,5 @@ message SignInResponse { string uid = 1; string name = 2; string email = 3; + string token = 4; } diff --git a/rust-lib/flowy-user/src/services/user/user_server.rs b/rust-lib/flowy-user/src/services/user/user_server.rs index 09af14e703..e8384ce01c 100644 --- a/rust-lib/flowy-user/src/services/user/user_server.rs +++ b/rust-lib/flowy-user/src/services/user/user_server.rs @@ -81,6 +81,7 @@ impl UserServer for UserServerMock { uid, name: params.email.clone(), email: params.email, + token: "".to_string(), }) }) }