[server]: fix expired duration

This commit is contained in:
appflowy 2021-09-16 13:41:07 +08:00
parent e2e47e8df0
commit 420d90c221
13 changed files with 135 additions and 44 deletions

View File

@ -40,7 +40,7 @@ class SplashScreen extends StatelessWidget {
(workspace) => getIt<ISplashRoute>()
.pushHomeScreen(context, userProfile, workspace.id),
(error) async {
assert(error.code == workspace.ErrorCode.CurrentWorkspaceNotFound);
assert(error.code == workspace.ErrorCode.RecordNotFound);
getIt<ISplashRoute>().pushWelcomeScreen(context, userProfile);
},
);

View File

@ -24,10 +24,23 @@ bytes = "1"
toml = "0.5.8"
dashmap = "4.0"
log = "0.4.14"
# tracing
tracing = { version = "0.1", features = ["log"] }
tracing-futures = "0.2.4"
tracing-subscriber = { version = "0.2.12", features = ["registry", "env-filter", "ansi", "json"] }
tracing-bunyan-formatter = "0.2.2"
tracing-appender = "0.1"
tracing-core = "0.1"
tracing-log = { version = "0.1.1"}
# serde
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
serde-aux = "1.0.1"
derive_more = {version = "0.99"}
protobuf = {version = "2.20.0"}
uuid = { version = "0.8", features = ["serde", "v4"] }
@ -41,8 +54,6 @@ sql-builder = "3.1.1"
lazy_static = "1.4"
tokio = { version = "1", features = ["full"] }
flowy-log = { path = "../rust-lib/flowy-log" }
flowy-user = { path = "../rust-lib/flowy-user" }
flowy-workspace = { path = "../rust-lib/flowy-workspace" }
flowy-document = { path = "../rust-lib/flowy-document" }

View File

@ -82,7 +82,7 @@ async fn period_check(_pool: Data<PgPool>) {
}
}
fn ws_scope() -> Scope { web::scope("/ws").service(ws_service::router::start_connection) }
fn ws_scope() -> Scope { web::scope("/ws").service(ws_service::router::establish_ws_connection) }
fn user_scope() -> Scope {
// https://developer.mozilla.org/en-US/docs/Web/HTTP
@ -132,7 +132,9 @@ fn user_scope() -> Scope {
}
async fn init_app_context(configuration: &Settings) -> AppContext {
let _ = flowy_log::Builder::new("flowy").env_filter("Debug").build();
let _ = crate::service::log::Builder::new("flowy")
.env_filter("Debug")
.build();
let pg_pool = get_connection_pool(&configuration.database)
.await
.expect(&format!(

View File

@ -28,7 +28,7 @@ impl Claim {
sub: "auth".to_string(),
user_id: user_id.to_string(),
iat: Local::now().timestamp(),
exp: (Local::now() + Duration::hours(24)).timestamp(),
exp: (Local::now() + Duration::days(EXPIRED_DURATION_DAYS)).timestamp(),
}
}
@ -74,6 +74,7 @@ impl Token {
}
}
use crate::service::user_service::EXPIRED_DURATION_DAYS;
use actix_web::{dev::Payload, FromRequest, HttpRequest};
use flowy_net::config::HEADER_TOKEN;
use futures::future::{ready, Ready};

View File

@ -65,11 +65,17 @@ where
if !authenticate_pass {
if let Some(header) = req.headers().get(HEADER_TOKEN) {
let result: Result<LoggedUser, ServerError> = header.try_into();
if let Ok(logged_user) = result {
if AUTHORIZED_USERS.is_authorized(&logged_user) {
authenticate_pass = true;
}
match result {
Ok(logged_user) => {
authenticate_pass = AUTHORIZED_USERS.is_authorized(&logged_user);
// Update user timestamp
AUTHORIZED_USERS.store_auth(logged_user, true);
},
Err(e) => log::error!("{:?}", e),
}
} else {
log::debug!("Can't find any token from request: {:?}", req);
}
}

View File

@ -0,0 +1,50 @@
use log::LevelFilter;
use std::path::Path;
use tracing::subscriber::set_global_default;
use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
use tracing_log::LogTracer;
use tracing_subscriber::{layer::SubscriberExt, EnvFilter};
pub struct Builder {
name: String,
env_filter: String,
}
impl Builder {
pub fn new(name: &str) -> Self {
Builder {
name: name.to_owned(),
env_filter: "Info".to_owned(),
}
}
pub fn env_filter(mut self, env_filter: &str) -> Self {
self.env_filter = env_filter.to_owned();
self
}
pub fn build(self) -> std::result::Result<(), String> {
let env_filter = EnvFilter::new(self.env_filter);
let subscriber = tracing_subscriber::fmt()
.with_target(true)
.with_max_level(tracing::Level::DEBUG)
.with_writer(std::io::stderr)
.with_thread_ids(false)
.compact()
.finish()
.with(env_filter);
let formatting_layer = BunyanFormattingLayer::new(self.name.clone(), std::io::stdout);
let _ = set_global_default(subscriber.with(JsonStorageLayer).with(formatting_layer))
.map_err(|e| format!("{:?}", e))?;
let _ = LogTracer::builder()
.with_max_level(LevelFilter::Debug)
.init()
.map_err(|e| format!("{:?}", e))
.unwrap();
Ok(())
}
}

View File

@ -1,4 +1,5 @@
pub mod doc_service;
pub(crate) mod log;
pub mod user_service;
pub(crate) mod util;
pub mod workspace_service;

View File

@ -33,10 +33,7 @@ impl LoggedUser {
pub fn from_token(token: String) -> Result<Self, ServerError> {
let user: LoggedUser = Token::decode_token(&token.into())?.into();
match AUTHORIZED_USERS.is_authorized(&user) {
true => Ok(user),
false => Err(ServerError::unauthorized()),
}
Ok(user)
}
pub fn get_user_id(&self) -> Result<uuid::Uuid, ServerError> {
@ -68,7 +65,10 @@ impl std::convert::TryFrom<&HeaderValue> for LoggedUser {
fn try_from(header: &HeaderValue) -> Result<Self, Self::Error> {
match header.to_str() {
Ok(val) => LoggedUser::from_token(val.to_owned()),
Err(_) => Err(ServerError::unauthorized()),
Err(e) => {
log::error!("Header to string failed: {:?}", e);
Err(ServerError::unauthorized())
},
}
}
}
@ -79,27 +79,36 @@ enum AuthStatus {
NotAuthorized,
}
pub const EXPIRED_DURATION_DAYS: i64 = 5;
pub struct AuthorizedUsers(DashMap<LoggedUser, AuthStatus>);
impl AuthorizedUsers {
pub fn new() -> Self { Self(DashMap::new()) }
pub fn is_authorized(&self, user: &LoggedUser) -> bool {
match self.0.get(user) {
None => false,
None => {
log::debug!("user not login yet or server was reboot");
false
},
Some(status) => match *status {
AuthStatus::Authorized(last_time) => {
let current_time = Utc::now();
(current_time - last_time).num_days() < 5
let days = (current_time - last_time).num_days();
log::debug!("user active {} from now", days);
days < EXPIRED_DURATION_DAYS
},
AuthStatus::NotAuthorized => {
log::debug!("user logout already");
false
},
AuthStatus::NotAuthorized => false,
},
}
}
pub fn store_auth(&self, user: LoggedUser, is_auth: bool) -> Result<(), ServerError> {
let current_time = Utc::now();
let status = if is_auth {
AuthStatus::Authorized(current_time)
AuthStatus::Authorized(Utc::now())
} else {
AuthStatus::NotAuthorized
};

View File

@ -11,7 +11,7 @@ use actix_web::{
use actix_web_actors::ws;
#[get("/{token}")]
pub async fn start_connection(
pub async fn establish_ws_connection(
request: HttpRequest,
payload: Payload,
path: Path<String>,

View File

@ -44,8 +44,17 @@ impl FlowyDocument {
}
pub async fn open(&self, params: QueryDocParams, pool: Arc<ConnectionPool>) -> Result<Doc, DocError> {
let doc = self.controller.open(params, pool).await?;
let _ = self.cache.open(&doc.id, doc.data.clone())?;
let doc = match self.cache.is_opened(&params.doc_id) {
true => {
let data = self.cache.read_doc(&params.doc_id).await?;
Doc { id: params.doc_id, data }
},
false => {
let doc = self.controller.open(params, pool).await?;
let _ = self.cache.open(&doc.id, doc.data.clone())?;
doc
},
};
Ok(doc)
}
@ -64,16 +73,8 @@ impl FlowyDocument {
})
.await?;
let doc_str = match self.cache.read_doc(&params.id).await? {
None => "".to_owned(),
Some(doc_json) => doc_json,
};
let doc = Doc {
id: params.id,
data: doc_str.as_bytes().to_vec(),
};
let data = self.cache.read_doc(&params.id).await?;
let doc = Doc { id: params.id, data };
Ok(doc)
}
}

View File

@ -38,6 +38,14 @@ impl DocCache {
Ok(())
}
pub(crate) fn is_opened<T>(&self, id: T) -> bool
where
T: Into<DocId>,
{
let doc_id = id.into();
self.inner.get(&doc_id).is_some()
}
pub(crate) async fn mut_doc<T, F>(&self, id: T, f: F) -> Result<(), DocError>
where
T: Into<DocId>,
@ -53,19 +61,19 @@ impl DocCache {
}
}
pub(crate) async fn read_doc<T>(&self, id: T) -> Result<Option<String>, DocError>
pub(crate) async fn read_doc<T>(&self, id: T) -> Result<Vec<u8>, DocError>
where
T: Into<DocId>,
T: Into<DocId> + Clone,
{
let doc_id = id.into();
match self.inner.get(&doc_id) {
None => Err(doc_not_found()),
Some(doc_info) => {
let write_guard = doc_info.read().await;
let doc = &(*write_guard).document;
Ok(Some(doc.to_json()))
},
if self.is_opened(id.clone()) {
return Err(doc_not_found());
}
let doc_id = id.into();
let doc_info = self.inner.get(&doc_id).unwrap();
let write_guard = doc_info.read().await;
let doc = &(*write_guard).document;
Ok(doc.to_bytes())
}
pub(crate) fn close<T>(&self, id: T) -> Result<(), DocError>

View File

@ -11,7 +11,7 @@ impl ResponseMiddleware for DocMiddleware {
fn receive_response(&self, token: &Option<String>, response: &FlowyResponse) {
if let Some(error) = &response.error {
if error.is_unauthorized() {
log::error!("workspace user is unauthorized");
log::error!("doc user is unauthorized");
match token {
None => {},

View File

@ -51,6 +51,8 @@ impl Document {
pub fn to_json(&self) -> String { self.delta.to_json() }
pub fn to_bytes(&self) -> Vec<u8> { self.delta.clone().into_bytes() }
pub fn to_string(&self) -> String { self.delta.apply("").unwrap() }
pub fn apply_changeset<T>(&mut self, changeset: T) -> Result<(), OTError>