use std::sync::Arc;
use musli_web05::api::DecodeBody;
use musli_web05::ws::{self, Incoming, Outgoing};
use super::{
AuthUpdate, CloudflareRequest, DeleteUserRequest, IssueLinkRequest, MeRequest, Request,
RequestError, RevokeAccessRequest, RevokeLinkRequest, SignOutUserRequest, Updates, UserUpdate,
UsersRequest,
};
use crate::Auth;
use crate::axum08::SignedIn;
#[cfg(doc)]
use crate::core::Error;
use crate::core::{AuthEvent, Caller, Result};
use crate::store::{Session, User};
use crate::types::{Access, AuthError, AuthRequest, UserId};
#[derive(Clone)]
pub struct Connection {
auth: Auth,
signed_in: Arc<SignedIn>,
}
impl Connection {
pub fn new(auth: Auth, signed_in: &SignedIn) -> Self {
Self {
auth,
signed_in: Arc::new(signed_in.clone()),
}
}
pub fn updates(&self) -> Updates {
Updates::new(self.clone())
}
pub(super) fn auth(&self) -> &Auth {
&self.auth
}
pub(super) fn upgrade(&self) -> &SignedIn {
&self.signed_in
}
pub async fn update(&self, event: &AuthEvent) -> Result<Option<AuthUpdate>> {
let update = match event {
AuthEvent::SessionEnded(_) | AuthEvent::UserSessionsEnded { .. } => return Ok(None),
AuthEvent::UserChanged(info) if info.id == self.user_id() => {
let Some((user, session)) = self.auth.session(self.session_id()).await? else {
return Ok(None);
};
let info = self.auth.session_info(&user, &session).await?;
return Ok(Some(AuthUpdate::Me(info)));
}
AuthEvent::UserChanged(info) => AuthUpdate::UserChanged(info.clone()),
AuthEvent::UserCreated(info) => AuthUpdate::UserCreated(info.clone()),
AuthEvent::UserDeleted(id) => AuthUpdate::UserDeleted(*id),
};
let Some((user, _)) = self.auth.session(self.session_id()).await? else {
return Ok(None);
};
if user.access != Access::Active || !user.is_admin() {
return Ok(None);
}
Ok(Some(update))
}
pub async fn lagged(&self) -> Result<Option<AuthUpdate>> {
let Some((user, session)) = self.auth.session(self.session_id()).await? else {
return Ok(None);
};
let info = self.auth.session_info(&user, &session).await?;
if user.access == Access::Active && user.is_admin() {
Ok(Some(AuthUpdate::Resync(info)))
} else {
Ok(Some(AuthUpdate::Me(info)))
}
}
pub async fn handle(
&self,
request: Request,
incoming: &mut Incoming<'_>,
outgoing: &mut Outgoing<'_>,
) -> Result<bool, RequestError> {
let result = self.dispatch(request, incoming, outgoing).await;
if let Err(error) = &result
&& error.auth_error().is_none()
{
tracing::error!(error = ?error, "auth: socket request {request:?} failed: {error}");
}
Ok(result?)
}
async fn dispatch(
&self,
request: Request,
incoming: &mut Incoming<'_>,
outgoing: &mut Outgoing<'_>,
) -> Result<bool> {
let Some(request) = decode(request, incoming) else {
return Ok(false);
};
debug_assert_eq!(request.cookie(), None);
let caller = Caller::new().session(self.session_id());
let dispatched = self.auth.dispatch(&caller, request).await?;
outgoing.write(dispatched.response);
Ok(true)
}
pub fn user_id(&self) -> UserId {
self.signed_in.user.id
}
pub fn session_id(&self) -> &str {
&self.signed_in.session.id
}
pub async fn signed_in(&self) -> Result<(User, Session)> {
match self.auth.session(self.session_id()).await? {
Some(signed_in) => Ok(signed_in),
None => Err(AuthError::Unauthorized.into()),
}
}
pub async fn require_admin(&self) -> Result<User> {
let (user, _) = self.admin_session().await?;
Ok(user)
}
async fn admin_session(&self) -> Result<(User, Session)> {
let (user, session) = self.signed_in().await?;
if user.access != Access::Active || !user.is_admin() {
return Err(AuthError::Forbidden.into());
}
Ok((user, session))
}
}
pub(super) trait ReadBody {
fn read<T>(&mut self) -> Option<T>
where
T: for<'de> DecodeBody<'de> + 'static;
}
impl ReadBody for Incoming<'_> {
fn read<T>(&mut self) -> Option<T>
where
T: for<'de> DecodeBody<'de> + 'static,
{
Incoming::read(self)
}
}
pub(super) fn decode(request: Request, body: &mut impl ReadBody) -> Option<AuthRequest> {
let request = match request {
Request::Me => {
let MeRequest = body.read()?;
AuthRequest::Me
}
Request::UpdateAccount => AuthRequest::UpdateAccount(body.read()?),
Request::ChangePassword => AuthRequest::ChangePassword(body.read()?),
Request::Users => {
let UsersRequest = body.read()?;
AuthRequest::Users
}
Request::CreateUser => AuthRequest::CreateUser(body.read()?),
Request::UpdateUser => {
let UserUpdate { id, change } = body.read()?;
AuthRequest::UpdateUser { id, change }
}
Request::DeleteUser => {
let DeleteUserRequest { id } = body.read()?;
AuthRequest::DeleteUser { id }
}
Request::IssueLink => {
let IssueLinkRequest { id } = body.read()?;
AuthRequest::IssueLink { id }
}
Request::RevokeLink => {
let RevokeLinkRequest { id } = body.read()?;
AuthRequest::RevokeLink { id }
}
Request::SignOutUser => {
let SignOutUserRequest { id } = body.read()?;
AuthRequest::SignOutUser { id }
}
Request::RevokeAccess => {
let RevokeAccessRequest { id } = body.read()?;
AuthRequest::RevokeAccess { id }
}
Request::Cloudflare => {
let CloudflareRequest = body.read()?;
AuthRequest::Cloudflare
}
Request::SetCloudflare => AuthRequest::SetCloudflare(body.read()?),
Request::Unknown(_) => return None,
};
Some(request)
}
impl ws::Handler for Connection {
type Id = Request;
type Response = Result<bool, RequestError>;
fn handle<'this>(
&'this self,
id: Request,
incoming: &'this mut Incoming<'_>,
outgoing: &'this mut Outgoing<'_>,
) -> impl Future<Output = Self::Response> + Send + 'this {
Connection::handle(self, id, incoming, outgoing)
}
}