mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
[server]: fix expired duration
This commit is contained in:
parent
e2e47e8df0
commit
420d90c221
@ -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);
|
||||
},
|
||||
);
|
||||
|
@ -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" }
|
||||
|
@ -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!(
|
||||
|
@ -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};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
50
backend/src/service/log/mod.rs
Normal file
50
backend/src/service/log/mod.rs
Normal 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(())
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
pub mod doc_service;
|
||||
pub(crate) mod log;
|
||||
pub mod user_service;
|
||||
pub(crate) mod util;
|
||||
pub mod workspace_service;
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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>,
|
||||
|
@ -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(¶ms.doc_id) {
|
||||
true => {
|
||||
let data = self.cache.read_doc(¶ms.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(¶ms.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(¶ms.id).await?;
|
||||
let doc = Doc { id: params.id, data };
|
||||
Ok(doc)
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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 => {},
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user