fad06fd
use models::users::{user_devices, user_sessions};
use sea_orm::sea_query::Expr;
use sea_orm::{
ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, IntoActiveModel, QueryFilter, QueryOrder,
QuerySelect, Set, TransactionTrait,
};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use uuid::Uuid;
use super::AuthService;
use crate::errors::AuthError;
use crate::share::types::ListQuery;
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct DeviceDto {
pub id: Uuid,
pub device_name: Option<String>,
pub device_type: Option<String>,
pub os: Option<String>,
pub is_trusted: bool,
pub first_seen_at: OffsetDateTime,
pub last_seen_at: OffsetDateTime,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct UpdateDeviceCmd {
pub device_name: Option<String>,
pub device_type: Option<String>,
pub is_trusted: Option<bool>,
}
impl AuthService {
pub async fn list_devices(
&self,
uid: Uuid,
query: ListQuery,
) -> Result<Vec<DeviceDto>, AuthError> {
let (limit, offset) = query.page();
let rows = user_devices::Entity::find()
.filter(user_devices::Column::UserId.eq(uid))
.order_by_desc(user_devices::Column::LastSeenAt)
.limit(limit)
.offset(offset)
.all(self.db().read())
.await?;
Ok(rows
.into_iter()
.map(|row| DeviceDto {
id: row.id,
device_name: row.device_name,
device_type: row.device_type,
os: row.os,
is_trusted: row.is_trusted,
first_seen_at: row.first_seen_at,
last_seen_at: row.last_seen_at,
})
.collect())
}
pub async fn update_device(
&self,
uid: Uuid,
device_id: Uuid,
cmd: UpdateDeviceCmd,
) -> Result<(), AuthError> {
let db = self.db();
let Some(device) = user_devices::Entity::find()
.filter(user_devices::Column::Id.eq(device_id))
.filter(user_devices::Column::UserId.eq(uid))
.one(db.write())
.await?
else {
return Err(AuthError::NotFound);
};
let device_name = cmd.device_name.or(device.device_name.clone());
let device_type = cmd.device_type.or(device.device_type.clone());
let mut active = device.into_active_model();
active.device_name = Set(device_name);
active.device_type = Set(device_type);
if let Some(trusted) = cmd.is_trusted {
active.is_trusted = Set(trusted);
}
active.updated_at = Set(OffsetDateTime::now_utc());
active.update(db.write()).await?;
Ok(())
}
pub async fn delete_device(&self, uid: Uuid, device_id: Uuid) -> Result<(), AuthError> {
let db = self.db();
let device_exists = user_devices::Entity::find()
.filter(user_devices::Column::Id.eq(device_id))
.filter(user_devices::Column::UserId.eq(uid))
.one(db.write())
.await?;
if device_exists.is_none() {
return Err(AuthError::NotFound);
}
let now = OffsetDateTime::now_utc();
TransactionTrait::transaction(db.primary(), |txn| {
Box::pin(async move {
user_devices::Entity::delete_by_id(device_id)
.exec(txn)
.await?;
user_sessions::Entity::update_many()
.filter(user_sessions::Column::UserId.eq(uid))
.filter(user_sessions::Column::DeviceId.eq(Some(device_id)))
.col_expr(user_sessions::Column::IsRevoked, Expr::value(true))
.col_expr(user_sessions::Column::RevokedAt, Expr::value(Some(now)))
.col_expr(user_sessions::Column::UpdatedAt, Expr::value(now))
.exec(txn)
.await?;
Ok::<(), DbErr>(())
})
})
.await?;
Ok(())
}
}