mirror of
https://github.com/hexedtech/codemp.git
synced 2024-11-22 15:24:48 +01:00
feat: better auth: multiworkspace, proper flow
Co-authored-by: zaaarf <me@zaaarf.foo>
This commit is contained in:
parent
59d8a4640d
commit
893c3d31e0
2 changed files with 146 additions and 125 deletions
166
src/client.rs
166
src/client.rs
|
@ -2,29 +2,47 @@
|
|||
//!
|
||||
//! codemp client manager, containing grpc services
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use dashmap::DashMap;
|
||||
use tokio::sync::mpsc;
|
||||
use tonic::service::interceptor::InterceptedService;
|
||||
use tonic::service::Interceptor;
|
||||
use tonic::transport::{Channel, Endpoint};
|
||||
use tonic::IntoRequest;
|
||||
use uuid::Uuid;
|
||||
|
||||
use codemp_proto::auth::auth_client::AuthClient;
|
||||
use codemp_proto::{
|
||||
common::Empty,
|
||||
buffer::buffer_client::BufferClient,
|
||||
cursor::cursor_client::CursorClient,
|
||||
auth::{Token, WorkspaceJoinRequest},
|
||||
workspace::workspace_client::WorkspaceClient,
|
||||
};
|
||||
use crate::{
|
||||
api::controller::ControllerWorker,
|
||||
cursor::worker::CursorWorker,
|
||||
workspace::Workspace
|
||||
};
|
||||
use codemp_proto::auth::{Token, WorkspaceJoinRequest};
|
||||
use crate::workspace::Workspace;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AuthWrap {
|
||||
username: String,
|
||||
password: String,
|
||||
service: AuthClient<Channel>,
|
||||
}
|
||||
|
||||
impl AuthWrap {
|
||||
async fn try_new(username: &str, password: &str, host: &str) -> crate::Result<Self> {
|
||||
let channel = Endpoint::from_shared(host.to_string())?
|
||||
.connect()
|
||||
.await?;
|
||||
|
||||
Ok(AuthWrap {
|
||||
username: username.to_string(),
|
||||
password: password.to_string(),
|
||||
service: AuthClient::new(channel),
|
||||
})
|
||||
}
|
||||
|
||||
async fn login_workspace(&self, ws: &str) -> crate::Result<Token> {
|
||||
Ok(
|
||||
self.service.clone()
|
||||
.login(WorkspaceJoinRequest {
|
||||
username: self.username.clone(),
|
||||
password: self.password.clone(),
|
||||
workspace_id: Some(ws.to_string())
|
||||
})
|
||||
.await?
|
||||
.into_inner()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// codemp client manager
|
||||
///
|
||||
|
@ -33,113 +51,47 @@ use crate::{
|
|||
/// can be used to interact with server
|
||||
pub struct Client {
|
||||
user_id: Uuid,
|
||||
host: String,
|
||||
workspaces: DashMap<String, Workspace>,
|
||||
token_tx: Arc<tokio::sync::watch::Sender<Token>>,
|
||||
services: Arc<Services>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ClientInterceptor {
|
||||
token: tokio::sync::watch::Receiver<Token>
|
||||
}
|
||||
|
||||
impl Interceptor for ClientInterceptor {
|
||||
fn call(&mut self, mut request: tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status> {
|
||||
if let Ok(token) = self.token.borrow().token.parse() {
|
||||
request.metadata_mut().insert("auth", token);
|
||||
}
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Services {
|
||||
pub(crate) workspace: WorkspaceClient<InterceptedService<Channel, ClientInterceptor>>,
|
||||
pub(crate) buffer: BufferClient<InterceptedService<Channel, ClientInterceptor>>,
|
||||
pub(crate) cursor: CursorClient<InterceptedService<Channel, ClientInterceptor>>,
|
||||
pub(crate) auth: AuthClient<Channel>,
|
||||
}
|
||||
|
||||
// TODO meno losco
|
||||
fn parse_codemp_connection_string(string: &str) -> (String, String) {
|
||||
let url = string.replace("codemp://", "");
|
||||
let (host, workspace) = url.split_once('/').unwrap();
|
||||
(format!("http://{}", host), workspace.to_string())
|
||||
auth: AuthWrap,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// instantiate and connect a new client
|
||||
pub async fn new(dest: &str) -> crate::Result<Self> {
|
||||
let (_host, _workspace_id) = parse_codemp_connection_string(dest);
|
||||
|
||||
let channel = Endpoint::from_shared(dest.to_string())?
|
||||
.connect()
|
||||
.await?;
|
||||
|
||||
let (token_tx, token_rx) = tokio::sync::watch::channel(
|
||||
Token { token: "".to_string() }
|
||||
);
|
||||
|
||||
let inter = ClientInterceptor { token: token_rx };
|
||||
|
||||
let buffer = BufferClient::with_interceptor(channel.clone(), inter.clone());
|
||||
let cursor = CursorClient::with_interceptor(channel.clone(), inter.clone());
|
||||
let workspace = WorkspaceClient::with_interceptor(channel.clone(), inter.clone());
|
||||
let auth = AuthClient::new(channel);
|
||||
pub async fn new(
|
||||
host: impl AsRef<str>,
|
||||
username: impl AsRef<str>,
|
||||
password: impl AsRef<str>
|
||||
) -> crate::Result<Self> {
|
||||
let host = if host.as_ref().starts_with("http") {
|
||||
host.as_ref().to_string()
|
||||
} else {
|
||||
format!("https://{}", host.as_ref())
|
||||
};
|
||||
|
||||
let user_id = uuid::Uuid::new_v4();
|
||||
let auth = AuthWrap::try_new(username.as_ref(), password.as_ref(), &host).await?;
|
||||
|
||||
Ok(Client {
|
||||
user_id,
|
||||
host,
|
||||
workspaces: DashMap::default(),
|
||||
token_tx: Arc::new(token_tx),
|
||||
services: Arc::new(Services { workspace, buffer, cursor, auth })
|
||||
auth,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn login(&self, username: String, password: String, workspace_id: Option<String>) -> crate::Result<()> {
|
||||
Ok(self.token_tx.send(
|
||||
self.services.auth.clone()
|
||||
.login(WorkspaceJoinRequest { username, password, workspace_id})
|
||||
.await?
|
||||
.into_inner()
|
||||
)?)
|
||||
}
|
||||
|
||||
/// join a workspace, returns an [tokio::sync::RwLock] to interact with it
|
||||
pub async fn join_workspace(&self, workspace: &str) -> crate::Result<Workspace> {
|
||||
let ws_stream = self.services.workspace.clone().attach(Empty{}.into_request()).await?.into_inner();
|
||||
pub async fn join_workspace(&self, workspace: impl AsRef<str>) -> crate::Result<Workspace> {
|
||||
let token = self.auth.login_workspace(workspace.as_ref()).await?;
|
||||
|
||||
let (tx, rx) = mpsc::channel(256);
|
||||
let cur_stream = self.services.cursor.clone()
|
||||
.attach(tokio_stream::wrappers::ReceiverStream::new(rx))
|
||||
.await?
|
||||
.into_inner();
|
||||
|
||||
let worker = CursorWorker::default();
|
||||
let controller = worker.subscribe();
|
||||
tokio::spawn(async move {
|
||||
tracing::debug!("controller worker started");
|
||||
worker.work(tx, cur_stream).await;
|
||||
tracing::debug!("controller worker stopped");
|
||||
});
|
||||
|
||||
let ws = Workspace::new(
|
||||
workspace.to_string(),
|
||||
let ws = Workspace::try_new(
|
||||
workspace.as_ref().to_string(),
|
||||
self.user_id,
|
||||
controller,
|
||||
self.token_tx.clone(),
|
||||
self.services.clone()
|
||||
);
|
||||
&self.host,
|
||||
token.clone()
|
||||
).await?;
|
||||
|
||||
ws.fetch_users().await?;
|
||||
ws.fetch_buffers().await?;
|
||||
|
||||
ws.run_actor(ws_stream);
|
||||
|
||||
self.workspaces.insert(workspace.to_string(), ws.clone());
|
||||
self.workspaces.insert(workspace.as_ref().to_string(), ws.clone());
|
||||
|
||||
Ok(ws)
|
||||
}
|
||||
|
|
105
src/workspace.rs
105
src/workspace.rs
|
@ -1,12 +1,16 @@
|
|||
use crate::{
|
||||
api::{Controller, controller::ControllerWorker},
|
||||
api::{controller::ControllerWorker, Controller},
|
||||
buffer::{self, worker::BufferWorker},
|
||||
client::Services,
|
||||
cursor,
|
||||
cursor::{self, worker::CursorWorker},
|
||||
};
|
||||
|
||||
use codemp_proto::{
|
||||
common::Empty,
|
||||
buffer::buffer_client::BufferClient,
|
||||
cursor::cursor_client::CursorClient,
|
||||
auth::Token,
|
||||
common::{Empty, Identity},
|
||||
workspace::workspace_client::WorkspaceClient,
|
||||
common::Identity,
|
||||
files::BufferNode,
|
||||
workspace::{
|
||||
workspace_event::{
|
||||
|
@ -15,10 +19,11 @@ use codemp_proto::{
|
|||
WorkspaceEvent,
|
||||
},
|
||||
};
|
||||
|
||||
use dashmap::{DashMap, DashSet};
|
||||
use std::{collections::BTreeSet, sync::Arc};
|
||||
use tokio::sync::mpsc;
|
||||
use tonic::Streaming;
|
||||
use tonic::{service::{interceptor::InterceptedService, Interceptor}, transport::{Channel, Endpoint}, Streaming};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(feature = "js")]
|
||||
|
@ -30,6 +35,47 @@ pub struct UserInfo {
|
|||
pub uuid: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct WorkspaceInterceptor {
|
||||
token: tokio::sync::watch::Receiver<Token>
|
||||
}
|
||||
|
||||
impl Interceptor for WorkspaceInterceptor {
|
||||
fn call(&mut self, mut request: tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status> {
|
||||
if let Ok(token) = self.token.borrow().token.parse() {
|
||||
request.metadata_mut().insert("auth", token);
|
||||
}
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
|
||||
type AuthedService = InterceptedService<Channel, WorkspaceInterceptor>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Services {
|
||||
token: tokio::sync::watch::Sender<Token>,
|
||||
workspace: WorkspaceClient<AuthedService>,
|
||||
buffer: BufferClient<AuthedService>,
|
||||
cursor: CursorClient<AuthedService>,
|
||||
}
|
||||
|
||||
impl Services {
|
||||
async fn try_new(dest: &str, token: Token) -> crate::Result<Self> {
|
||||
let channel = Endpoint::from_shared(dest.to_string())?
|
||||
.connect()
|
||||
.await?;
|
||||
let (token_tx, token_rx) = tokio::sync::watch::channel(token);
|
||||
let inter = WorkspaceInterceptor { token: token_rx };
|
||||
Ok(Self {
|
||||
token: token_tx,
|
||||
buffer: BufferClient::with_interceptor(channel.clone(), inter.clone()),
|
||||
cursor: CursorClient::with_interceptor(channel.clone(), inter.clone()),
|
||||
workspace: WorkspaceClient::with_interceptor(channel.clone(), inter.clone()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "python", pyo3::pyclass)]
|
||||
#[cfg_attr(feature = "js", napi)]
|
||||
|
@ -43,29 +89,52 @@ struct WorkspaceInner {
|
|||
buffers: DashMap<String, buffer::Controller>,
|
||||
filetree: DashSet<String>,
|
||||
users: DashMap<Uuid, UserInfo>,
|
||||
token: Arc<tokio::sync::watch::Sender<Token>>, // shared
|
||||
services: Arc<Services>, // shared
|
||||
services: Services
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
/// create a new buffer and perform initial fetch operations
|
||||
pub(crate) fn new(
|
||||
pub(crate) async fn try_new(
|
||||
id: String,
|
||||
user_id: Uuid,
|
||||
cursor: cursor::Controller,
|
||||
token: Arc<tokio::sync::watch::Sender<Token>>,
|
||||
services: Arc<Services>,
|
||||
) -> Self {
|
||||
Self(Arc::new(WorkspaceInner {
|
||||
dest: &str,
|
||||
token: Token,
|
||||
) -> crate::Result<Self> {
|
||||
let services = Services::try_new(dest, token).await?;
|
||||
let ws_stream = services.workspace.clone()
|
||||
.attach(Empty{})
|
||||
.await?
|
||||
.into_inner();
|
||||
|
||||
let (tx, rx) = mpsc::channel(256);
|
||||
let cur_stream = services.cursor.clone()
|
||||
.attach(tokio_stream::wrappers::ReceiverStream::new(rx))
|
||||
.await?
|
||||
.into_inner();
|
||||
|
||||
let worker = CursorWorker::default();
|
||||
let controller = worker.controller();
|
||||
tokio::spawn(async move {
|
||||
tracing::debug!("controller worker started");
|
||||
worker.work(tx, cur_stream).await;
|
||||
tracing::debug!("controller worker stopped");
|
||||
});
|
||||
|
||||
let ws = Self(Arc::new(WorkspaceInner {
|
||||
id,
|
||||
user_id,
|
||||
token,
|
||||
cursor,
|
||||
cursor: controller,
|
||||
buffers: DashMap::default(),
|
||||
filetree: DashSet::default(),
|
||||
users: DashMap::default(),
|
||||
services,
|
||||
}))
|
||||
}));
|
||||
|
||||
ws.fetch_users().await?;
|
||||
ws.fetch_buffers().await?;
|
||||
ws.run_actor(ws_stream);
|
||||
|
||||
Ok(ws)
|
||||
}
|
||||
|
||||
pub(crate) fn run_actor(&self, mut stream: Streaming<WorkspaceEvent>) {
|
||||
|
@ -135,7 +204,7 @@ impl Workspace {
|
|||
path: path.to_string(),
|
||||
});
|
||||
let credentials = worskspace_client.access_buffer(request).await?.into_inner();
|
||||
self.0.token.send(credentials.token)?;
|
||||
self.0.services.token.send(credentials.token)?;
|
||||
|
||||
let (tx, rx) = mpsc::channel(256);
|
||||
let mut req = tonic::Request::new(tokio_stream::wrappers::ReceiverStream::new(rx));
|
||||
|
@ -154,7 +223,7 @@ impl Workspace {
|
|||
.into_inner();
|
||||
|
||||
let worker = BufferWorker::new(self.0.user_id, path);
|
||||
let controller = worker.subscribe();
|
||||
let controller = worker.controller();
|
||||
tokio::spawn(async move {
|
||||
tracing::debug!("controller worker started");
|
||||
worker.work(tx, stream).await;
|
||||
|
|
Loading…
Reference in a new issue