save user password with bcrypt

This commit is contained in:
appflowy 2021-08-23 18:39:10 +08:00
parent f626f6f638
commit 15f1267956
27 changed files with 491 additions and 163 deletions

View File

@ -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);
}

View File

@ -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=');

View File

@ -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" }

View File

@ -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))

View 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 }

View File

@ -1,5 +1,6 @@
mod configuration;
mod const_define;
pub mod env;
pub use configuration::*;
pub use const_define::*;

View File

@ -1 +1,2 @@
pub mod token;
pub mod user;

View 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))?
}
}

View 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,
}

View File

@ -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::*;

View File

@ -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())
}

View 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!()
}

View File

@ -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 {

View File

@ -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, &params.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)
}

View 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, &params.email).await?;
transaction
.commit()
.await
.context("Failed to commit SQL transaction to sign in.")?;
match verify_password(&params.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, &params.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(&params.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)
}

View File

@ -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;

View 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(),
}),
}
}

View File

@ -64,7 +64,6 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
| "EditorEvent"
| "DocErrorCode"
| "FFIStatusCode"
| "UserServerError"
| "UserStatus"
| "UserEvent"
| "UserErrCode"

View File

@ -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);
}

View File

@ -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,
}

View File

@ -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))
}
}

View File

@ -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 {

View File

@ -30,6 +30,9 @@ pub struct SignInResponse {
#[pb(index = 3)]
pub email: String,
#[pb(index = 4)]
pub token: String,
}
impl TryInto<SignInParams> for SignInRequest {

View File

@ -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;

View File

@ -12,4 +12,5 @@ message SignInResponse {
string uid = 1;
string name = 2;
string email = 3;
string token = 4;
}

View File

@ -81,6 +81,7 @@ impl UserServer for UserServerMock {
uid,
name: params.email.clone(),
email: params.email,
token: "".to_string(),
})
})
}