scufflecloud_core/models/
mfa.rs

1use std::collections::HashSet;
2use std::sync::Arc;
3
4use diesel::Selectable;
5use diesel::prelude::{AsChangeset, Associations, Identifiable, Insertable, Queryable};
6
7use crate::CoreConfig;
8use crate::cedar::CedarEntity;
9use crate::chrono_ext::ChronoDateTimeExt;
10use crate::id::{Id, PrefixedId};
11use crate::models::users::{User, UserId};
12
13pub(crate) type MfaTotpCredentialId = Id<MfaTotpCredential>;
14
15#[derive(Queryable, Selectable, Insertable, Identifiable, AsChangeset, Associations, Debug, serde::Serialize)]
16#[diesel(table_name = crate::schema::mfa_totp_credentials)]
17#[diesel(belongs_to(User))]
18#[diesel(check_for_backend(diesel::pg::Pg))]
19pub struct MfaTotpCredential {
20    pub id: MfaTotpCredentialId,
21    pub user_id: UserId,
22    pub name: String,
23    pub secret: Vec<u8>,
24    pub last_used_at: chrono::DateTime<chrono::Utc>,
25}
26
27impl PrefixedId for MfaTotpCredential {
28    const PREFIX: &'static str = "mft";
29}
30
31impl<G: CoreConfig> CedarEntity<G> for MfaTotpCredential {
32    const ENTITY_TYPE: &'static str = "MfaTotpCredential";
33
34    fn entity_id(&self) -> cedar_policy::EntityId {
35        cedar_policy::EntityId::new(self.id.to_string_unprefixed())
36    }
37
38    async fn parents(&self, _global: &Arc<G>) -> Result<HashSet<cedar_policy::EntityUid>, tonic::Status> {
39        Ok(std::iter::once(CedarEntity::<G>::entity_uid(&self.user_id)).collect())
40    }
41}
42
43impl From<MfaTotpCredential> for pb::scufflecloud::core::v1::TotpCredential {
44    fn from(value: MfaTotpCredential) -> Self {
45        Self {
46            id: value.id.to_string(),
47            user_id: value.user_id.to_string(),
48            name: value.name,
49            last_used_at_utc: Some(value.last_used_at.to_prost_timestamp_utc()),
50        }
51    }
52}
53
54#[derive(Queryable, Selectable, Insertable, Identifiable, AsChangeset, Associations, Debug)]
55#[diesel(table_name = crate::schema::mfa_totp_reg_sessions)]
56#[diesel(primary_key(user_id))]
57#[diesel(belongs_to(User))]
58#[diesel(check_for_backend(diesel::pg::Pg))]
59pub struct MfaTotpRegistrationSession {
60    pub user_id: UserId,
61    pub secret: Vec<u8>,
62    pub expires_at: chrono::DateTime<chrono::Utc>,
63}
64
65pub(crate) type MfaWebauthnCredentialId = Id<MfaWebauthnCredential>;
66
67#[derive(Queryable, Selectable, Insertable, Identifiable, AsChangeset, Associations, Debug, serde::Serialize)]
68#[diesel(table_name = crate::schema::mfa_webauthn_credentials)]
69#[diesel(belongs_to(User))]
70#[diesel(check_for_backend(diesel::pg::Pg))]
71pub struct MfaWebauthnCredential {
72    pub id: MfaWebauthnCredentialId,
73    pub user_id: UserId,
74    pub name: String,
75    pub credential_id: Vec<u8>,
76    pub credential: serde_json::Value,
77    pub counter: Option<i64>,
78    pub last_used_at: chrono::DateTime<chrono::Utc>,
79}
80
81impl PrefixedId for MfaWebauthnCredential {
82    const PREFIX: &'static str = "mfw";
83}
84
85impl<G: CoreConfig> CedarEntity<G> for MfaWebauthnCredential {
86    const ENTITY_TYPE: &'static str = "MfaWebauthnCredential";
87
88    fn entity_id(&self) -> cedar_policy::EntityId {
89        cedar_policy::EntityId::new(self.id.to_string_unprefixed())
90    }
91
92    async fn parents(&self, _global: &Arc<G>) -> Result<HashSet<cedar_policy::EntityUid>, tonic::Status> {
93        Ok(std::iter::once(CedarEntity::<G>::entity_uid(&self.user_id)).collect())
94    }
95}
96
97impl From<MfaWebauthnCredential> for pb::scufflecloud::core::v1::WebauthnCredential {
98    fn from(value: MfaWebauthnCredential) -> Self {
99        Self {
100            id: value.id.to_string(),
101            user_id: value.user_id.to_string(),
102            name: value.name,
103            last_used_at_utc: Some(value.last_used_at.to_prost_timestamp_utc()),
104        }
105    }
106}
107
108#[derive(Queryable, Selectable, Insertable, Identifiable, AsChangeset, Associations, Debug)]
109#[diesel(table_name = crate::schema::mfa_webauthn_reg_sessions)]
110#[diesel(primary_key(user_id))]
111#[diesel(belongs_to(User))]
112#[diesel(check_for_backend(diesel::pg::Pg))]
113pub struct MfaWebauthnRegistrationSession {
114    pub user_id: UserId,
115    pub state: serde_json::Value,
116    pub expires_at: chrono::DateTime<chrono::Utc>,
117}
118
119#[derive(Queryable, Selectable, Insertable, Identifiable, AsChangeset, Associations, Debug)]
120#[diesel(table_name = crate::schema::mfa_webauthn_auth_sessions)]
121#[diesel(primary_key(user_id))]
122#[diesel(belongs_to(User))]
123#[diesel(check_for_backend(diesel::pg::Pg))]
124pub struct MfaWebauthnAuthenticationSession {
125    pub user_id: UserId,
126    pub state: serde_json::Value,
127    pub expires_at: chrono::DateTime<chrono::Utc>,
128}
129
130pub(crate) type MfaRecoveryCodeId = Id<MfaRecoveryCode>;
131
132#[derive(Queryable, Selectable, Insertable, Identifiable, AsChangeset, Associations, Debug)]
133#[diesel(table_name = crate::schema::mfa_recovery_codes)]
134#[diesel(primary_key(id))]
135#[diesel(belongs_to(User))]
136#[diesel(check_for_backend(diesel::pg::Pg))]
137pub struct MfaRecoveryCode {
138    pub id: MfaRecoveryCodeId,
139    pub user_id: UserId,
140    pub code_hash: String,
141}
142
143impl PrefixedId for MfaRecoveryCode {
144    const PREFIX: &'static str = "mrc";
145}