mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
save user password with bcrypt
This commit is contained in:
parent
f626f6f638
commit
15f1267956
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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=');
|
||||
|
@ -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" }
|
||||
|
@ -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<Server, std::io
|
||||
let AppContext { ws_server, pg_pool } = app_ctx;
|
||||
let ws_server = Data::new(ws_server);
|
||||
let pg_pool = Data::new(pg_pool);
|
||||
let domain = domain();
|
||||
let secret: String = secret();
|
||||
|
||||
let server = HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap(middleware::Logger::default())
|
||||
.wrap(identify_service(&domain, &secret))
|
||||
.app_data(web::JsonConfig::default().limit(4096))
|
||||
.service(ws_scope())
|
||||
.service(user_scope())
|
||||
@ -49,10 +58,24 @@ pub fn run(listener: TcpListener, app_ctx: AppContext) -> Result<Server, std::io
|
||||
Ok(server)
|
||||
}
|
||||
|
||||
fn ws_scope() -> 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<CookieIdentityPolicy> {
|
||||
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<PgPool, sqlx::Error> {
|
||||
PgPoolOptions::new()
|
||||
.connect_timeout(std::time::Duration::from_secs(5))
|
||||
|
9
backend/src/config/env.rs
Normal file
9
backend/src/config/env.rs
Normal file
@ -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 }
|
@ -1,5 +1,6 @@
|
||||
mod configuration;
|
||||
mod const_define;
|
||||
pub mod env;
|
||||
|
||||
pub use configuration::*;
|
||||
pub use const_define::*;
|
||||
|
@ -1 +1,2 @@
|
||||
|
||||
pub mod token;
|
||||
pub mod user;
|
||||
|
66
backend/src/entities/token.rs
Normal file
66
backend/src/entities/token.rs
Normal file
@ -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<Claim> 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<Self, ServerError> {
|
||||
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<Claim, ServerError> {
|
||||
decode::<Claim>(
|
||||
&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))?
|
||||
}
|
||||
}
|
8
backend/src/entities/user.rs
Normal file
8
backend/src/entities/user.rs
Normal file
@ -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,
|
||||
}
|
@ -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::*;
|
||||
|
@ -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<PgPool>,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
let params: SignUpParams = parse_from_payload(payload).await?;
|
||||
let resp = sign_up(pool.get_ref(), params).await?;
|
||||
|
||||
Ok(resp.into())
|
||||
}
|
57
backend/src/routers/user_router.rs
Normal file
57
backend/src/routers/user_router.rs
Normal file
@ -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<PgPool>,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
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<HttpResponse, ServerError> {
|
||||
id.forget();
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
pub async fn user_profile(
|
||||
request: HttpRequest,
|
||||
payload: Payload,
|
||||
pool: Data<PgPool>,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub async fn register_handler(
|
||||
_request: HttpRequest,
|
||||
payload: Payload,
|
||||
pool: Data<PgPool>,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
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<PgPool>,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
unimplemented!()
|
||||
}
|
@ -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<T: Message>(bytes: &[u8]) -> Result<T, ServerError> {
|
||||
pub async fn poll_payload(mut payload: web::Payload) -> Result<web::BytesMut, ServerError> {
|
||||
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 {
|
@ -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<FlowyResponse, ServerError> {
|
||||
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<SignUpResponse, ServerError> {
|
||||
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)
|
||||
}
|
132
backend/src/user_service/auth_service.rs
Normal file
132
backend/src/user_service/auth_service.rs
Normal file
@ -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<FlowyResponse, ServerError> {
|
||||
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<FlowyResponse, ServerError> {
|
||||
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<User, ServerError> {
|
||||
let user = sqlx::query_as::<Postgres, User>("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<SignUpResponse, ServerError> {
|
||||
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)
|
||||
}
|
@ -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;
|
||||
|
24
backend/src/user_service/utils.rs
Normal file
24
backend/src/user_service/utils.rs
Normal file
@ -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<String, ServerError> {
|
||||
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<bool, ServerError> {
|
||||
match verify(source, hash) {
|
||||
Ok(true) => Ok(true),
|
||||
_ => Err(ServerError {
|
||||
code: Code::PasswordNotMatch,
|
||||
msg: "Username and password don't match".to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
@ -64,7 +64,6 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
|
||||
| "EditorEvent"
|
||||
| "DocErrorCode"
|
||||
| "FFIStatusCode"
|
||||
| "UserServerError"
|
||||
| "UserStatus"
|
||||
| "UserEvent"
|
||||
| "UserErrCode"
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -14,9 +14,11 @@ pub struct ServerError {
|
||||
macro_rules! static_error {
|
||||
($name:ident, $status:expr) => {
|
||||
#[allow(non_snake_case, missing_docs)]
|
||||
pub fn $name<T: Debug>(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<T: Debug>(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,
|
||||
}
|
||||
|
@ -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<Self, ServerError> {
|
||||
let (tx, rx) = oneshot::channel::<Result<Response, _>>();
|
||||
// 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<Bytes, ServerError> {
|
||||
Some(error) => Err(error),
|
||||
}
|
||||
} else {
|
||||
Err(ServerError::http(original))
|
||||
Err(ServerError::http().with_msg(original))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ impl std::convert::From<protobuf::ProtobufError> for ServerError {
|
||||
}
|
||||
|
||||
impl std::convert::From<RecvError> for ServerError {
|
||||
fn from(error: RecvError) -> Self { ServerError::internal(error) }
|
||||
fn from(error: RecvError) -> Self { ServerError::internal().with_msg(error) }
|
||||
}
|
||||
|
||||
impl std::convert::From<serde_json::Error> for ServerError {
|
||||
@ -46,7 +46,7 @@ impl std::convert::From<serde_json::Error> for ServerError {
|
||||
}
|
||||
|
||||
impl std::convert::From<anyhow::Error> 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<reqwest::Error> for ServerError {
|
||||
|
@ -30,6 +30,9 @@ pub struct SignInResponse {
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub email: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
impl TryInto<SignInParams> for SignInRequest {
|
||||
|
@ -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>(
|
||||
"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;
|
||||
|
@ -12,4 +12,5 @@ message SignInResponse {
|
||||
string uid = 1;
|
||||
string name = 2;
|
||||
string email = 3;
|
||||
string token = 4;
|
||||
}
|
||||
|
@ -81,6 +81,7 @@ impl UserServer for UserServerMock {
|
||||
uid,
|
||||
name: params.email.clone(),
|
||||
email: params.email,
|
||||
token: "".to_string(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user