Skip to main content

toro_auth_core/
identity.rs

1use std::str::FromStr;
2
3use actix_web::{
4    HttpResponse, Responder,
5    web::{Data, Json, Path, ServiceConfig, delete, get, post, put},
6};
7use async_trait::async_trait;
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10
11use crate::{IntoPublic, ObjectId, session::SessionRes};
12
13pub enum IdentityError {
14    NotFound,
15    InternalServerError,
16    ServiceUnavailable,
17    Unauthorized,
18    InvalidId,
19    UsernameAlreadyInUse,
20}
21
22impl From<IdentityError> for HttpResponse {
23    fn from(value: IdentityError) -> Self {
24        match value {
25            IdentityError::InternalServerError => HttpResponse::InternalServerError().finish(),
26            IdentityError::NotFound => HttpResponse::NotFound().finish(),
27            IdentityError::ServiceUnavailable => HttpResponse::ServiceUnavailable().finish(),
28            IdentityError::Unauthorized => HttpResponse::Unauthorized().finish(),
29            IdentityError::InvalidId => HttpResponse::BadRequest().finish(),
30            IdentityError::UsernameAlreadyInUse => HttpResponse::Conflict().finish(),
31        }
32    }
33}
34
35#[derive(Deserialize)]
36pub struct IdentityGetPath {
37    id: String,
38}
39
40#[derive(Clone)]
41pub struct IdentityProvider<T>
42where
43    T: IntoPublic
44        + ObjectId
45        + Serialize
46        + for<'de> Deserialize<'de>
47        + Clone
48        + Send
49        + Sync
50        + 'static,
51{
52    identity_base_path: String,
53    backend: Data<Box<dyn IdentityBackend<T>>>,
54}
55
56impl<
57    T: IntoPublic + ObjectId + Serialize + for<'de> Deserialize<'de> + Clone + Send + Sync + 'static,
58> IdentityProvider<T>
59{
60    pub fn default_with_backend(backend: Data<Box<dyn IdentityBackend<T>>>) -> Self {
61        Self {
62            identity_base_path: String::from("identity"),
63            backend,
64        }
65    }
66
67    pub fn configure(&self, cfg: &mut ServiceConfig) {
68        let data = Data::new(self.clone());
69        cfg.app_data(data.clone())
70            .route(&data.identity_base_path, get().to(get_all::<T>))
71            .route(&data.identity_base_path, post().to(create::<T>))
72            .route(
73                &format!("{}/{{id}}", data.identity_base_path),
74                get().to(get_by_id::<T>),
75            )
76            .route(
77                &format!("{}/{{id}}", data.identity_base_path),
78                put().to(update_by_id::<T>),
79            )
80            .route(
81                &format!("{}/{{id}}", data.identity_base_path),
82                delete().to(delete_by_id::<T>),
83            );
84    }
85
86    pub async fn get_all(&self) -> Result<Vec<T>, IdentityError> {
87        self.backend.get_all().await
88    }
89
90    pub async fn create(&self, identity: T) -> Result<(), IdentityError> {
91        let by_username = self.backend.get_by_username(identity.username()).await?;
92        if by_username.is_some() {
93            return Err(IdentityError::UsernameAlreadyInUse);
94        }
95
96        self.backend.create(identity).await
97    }
98
99    pub async fn get_by_id(&self, id: String) -> Result<T, IdentityError> {
100        self.backend.get_by_id(id).await
101    }
102
103    pub async fn update(&self, id: String, identity: T) -> Result<(), IdentityError> {
104        self.backend.update_by_id(id, identity).await
105    }
106
107    pub async fn delete(&self, id: String) -> Result<(), IdentityError> {
108        self.backend.delete_by_id(id).await
109    }
110}
111
112async fn get_all<
113    T: IntoPublic + ObjectId + Serialize + for<'de> Deserialize<'de> + Clone + Send + Sync + 'static,
114>(
115    identity_provider: Data<IdentityProvider<T>>,
116) -> impl Responder {
117    match identity_provider.get_all().await {
118        Ok(result) => HttpResponse::Ok().json(
119            result
120                .into_iter()
121                .map(|res| res.into_public())
122                .collect::<Vec<T::Public>>(),
123        ),
124        Err(e) => e.into(),
125    }
126}
127
128async fn create<
129    T: IntoPublic + ObjectId + Serialize + for<'de> Deserialize<'de> + Clone + Send + Sync + 'static,
130>(
131    identity_provider: Data<IdentityProvider<T>>,
132    identity: Json<T>,
133) -> impl Responder {
134    match identity_provider.create(identity.0).await {
135        Ok(_) => HttpResponse::Created().finish(),
136        Err(e) => e.into(),
137    }
138}
139
140async fn get_by_id<
141    T: IntoPublic + ObjectId + Serialize + for<'de> Deserialize<'de> + Clone + Send + Sync + 'static,
142>(
143    identity_provider: Data<IdentityProvider<T>>,
144    path: Path<IdentityGetPath>,
145) -> impl Responder {
146    match identity_provider.get_by_id(path.id.clone()).await {
147        Ok(res) => HttpResponse::Ok().json(res.into_public()),
148        Err(e) => e.into(),
149    }
150}
151
152async fn update_by_id<
153    T: IntoPublic + ObjectId + Serialize + for<'de> Deserialize<'de> + Clone + Send + Sync + 'static,
154>(
155    identity_provider: Data<IdentityProvider<T>>,
156    path: Path<IdentityGetPath>,
157    identity: Json<T>,
158    session: SessionRes<T>,
159) -> impl Responder {
160    if session.inner.id() != Uuid::from_str(&path.id).ok() {
161        return HttpResponse::Unauthorized().finish();
162    }
163
164    match identity_provider.update(path.id.clone(), identity.0).await {
165        Ok(_) => HttpResponse::Ok().finish(),
166        Err(e) => e.into(),
167    }
168}
169
170async fn delete_by_id<
171    T: IntoPublic + ObjectId + Serialize + for<'de> Deserialize<'de> + Clone + Send + Sync + 'static,
172>(
173    identity_provider: Data<IdentityProvider<T>>,
174    path: Path<IdentityGetPath>,
175    session: SessionRes<T>,
176) -> impl Responder {
177    if session.inner.id() != Uuid::from_str(&path.id).ok() {
178        return HttpResponse::Unauthorized().finish();
179    }
180
181    match identity_provider.delete(path.id.clone()).await {
182        Ok(_) => HttpResponse::NoContent().finish(),
183        Err(e) => e.into(),
184    }
185}
186
187#[async_trait]
188pub trait IdentityBackend<T>: Send + Sync
189where
190    T: ObjectId + Serialize + for<'de> Deserialize<'de>,
191{
192    async fn get_all(&self) -> Result<Vec<T>, IdentityError>;
193    async fn create(&self, mut identity: T) -> Result<(), IdentityError>;
194    async fn get_by_id(&self, id: String) -> Result<T, IdentityError>;
195    async fn get_by_username(&self, username: String) -> Result<Option<T>, IdentityError>;
196    async fn update_by_id(&self, id: String, identity: T) -> Result<(), IdentityError>;
197    async fn delete_by_id(&self, id: String) -> Result<(), IdentityError>;
198}