reinhardt_testkit/auth/
builder.rs1use std::sync::Arc;
2use std::time::Duration;
3
4use uuid::Uuid;
5
6use super::error::TestAuthError;
7use super::identity::SessionIdentity;
8use super::secondary::SecondaryAuth;
9use super::secondary::TotpSecondaryAuth;
10use super::traits::ForceLoginUser;
11use crate::client::APIClient;
12
13pub struct AuthBuilder<'a> {
17 client: &'a APIClient,
18}
19
20impl<'a> AuthBuilder<'a> {
21 pub(crate) fn new(client: &'a APIClient) -> Self {
23 Self { client }
24 }
25
26 pub fn session(
32 self,
33 user: &impl ForceLoginUser,
34 backend: Arc<dyn reinhardt_middleware::session::AsyncSessionBackend>,
35 ) -> SessionAuthBuilder<'a> {
36 SessionAuthBuilder {
37 client: self.client,
38 identity: SessionIdentity::from_user(user),
39 backend,
40 ttl: Duration::from_secs(30 * 60),
41 secondary: vec![],
42 }
43 }
44
45 pub fn jwt(self, user: &impl ForceLoginUser, config: JwtTestConfig) -> JwtAuthBuilder<'a> {
47 JwtAuthBuilder {
48 client: self.client,
49 identity: SessionIdentity::from_user(user),
50 config,
51 secondary: vec![],
52 }
53 }
54}
55
56pub struct SessionAuthBuilder<'a> {
60 client: &'a APIClient,
61 identity: SessionIdentity,
62 backend: Arc<dyn reinhardt_middleware::session::AsyncSessionBackend>,
63 ttl: Duration,
64 secondary: Vec<Box<dyn SecondaryAuth>>,
65}
66
67impl<'a> SessionAuthBuilder<'a> {
68 pub fn with_staff(mut self, is_staff: bool) -> Self {
73 self.identity.is_staff = is_staff;
74 self
75 }
76
77 pub fn with_superuser(mut self, is_superuser: bool) -> Self {
79 self.identity.is_superuser = is_superuser;
80 self
81 }
82
83 pub fn with_ttl(mut self, ttl: Duration) -> Self {
85 self.ttl = ttl;
86 self
87 }
88
89 pub fn with_mfa_code(self, code: impl Into<String>) -> Self {
94 self.with_secondary(TotpSecondaryAuth::with_code_only(code))
95 }
96
97 pub fn with_secondary(mut self, auth: impl SecondaryAuth + 'static) -> Self {
99 self.secondary.push(Box::new(auth));
100 self
101 }
102
103 pub async fn apply(self) -> Result<(), TestAuthError> {
108 let session_id = Uuid::now_v7().to_string();
110
111 let session_data = self.identity.to_session_data(&session_id, self.ttl);
113
114 self.backend
116 .save(&session_data)
117 .await
118 .map_err(|e| TestAuthError::SessionError(e.to_string()))?;
119
120 self.client
122 .set_cookie("sessionid", &session_id)
123 .await
124 .map_err(|e| TestAuthError::ClientError(e.to_string()))?;
125
126 for secondary in &self.secondary {
128 secondary
129 .apply_to_client(self.client, &self.identity)
130 .await?;
131 }
132
133 Ok(())
134 }
135}
136
137pub struct JwtAuthBuilder<'a> {
141 client: &'a APIClient,
142 identity: SessionIdentity,
143 config: JwtTestConfig,
144 secondary: Vec<Box<dyn SecondaryAuth>>,
145}
146
147impl<'a> JwtAuthBuilder<'a> {
148 pub fn with_secondary(mut self, auth: impl SecondaryAuth + 'static) -> Self {
150 self.secondary.push(Box::new(auth));
151 self
152 }
153
154 pub async fn apply(self) -> Result<(), TestAuthError> {
158 use reinhardt_auth::jwt::{Claims, JwtAuth};
159
160 let claims = Claims::new(
162 self.identity.user_id.clone(),
163 self.identity.user_id.clone(),
164 chrono::Duration::seconds(self.config.expiry.as_secs() as i64),
165 self.identity.is_staff,
166 self.identity.is_superuser,
167 );
168
169 let jwt_auth = JwtAuth::new(self.config.secret.as_bytes());
171 let token = jwt_auth
172 .encode(&claims)
173 .map_err(|e| TestAuthError::JwtError(e.to_string()))?;
174
175 self.client
177 .set_header("Authorization", &format!("Bearer {token}"))
178 .await
179 .map_err(|e| TestAuthError::ClientError(e.to_string()))?;
180
181 for secondary in &self.secondary {
183 secondary
184 .apply_to_client(self.client, &self.identity)
185 .await?;
186 }
187
188 Ok(())
189 }
190}
191
192#[derive(Clone, Debug)]
194pub struct JwtTestConfig {
195 pub secret: String,
197 pub expiry: Duration,
199}
200
201impl Default for JwtTestConfig {
202 fn default() -> Self {
203 Self {
204 secret: "test-secret-key-for-testing-only".into(),
205 expiry: Duration::from_secs(3600),
206 }
207 }
208}