scufflecloud_core/models/
mfa.rs1use 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}