1use base64::prelude::*;
2use ring::rand::{SecureRandom, SystemRandom};
3use serde_json::Value;
4use sqlx::Row;
5use sqlx::sqlite::SqliteRow;
6use tracing::{debug, info};
7use uuid::Uuid;
8
9use crate::sqlite::db::Database;
10use crate::sqlite::nonce::now_secs;
11use crate::sqlite::order::{Identifier, rfc3339};
12use crate::sqlite::status::{self, AuthzStatus, ChallengeStatus};
13
14#[derive(Debug)]
38pub struct Authorization {
39 pub id: String,
40 pub order_id: String,
41 pub identifier: Identifier,
42 pub status: AuthzStatus,
43 pub expires: i64,
44 pub created_at: i64,
45}
46
47#[derive(Debug)]
59pub struct Challenge {
60 pub id: String,
61 pub authz_id: String,
62 pub typ: String,
63 pub token: String,
64 pub status: ChallengeStatus,
65 pub validated: Option<i64>,
66 pub error: Option<Value>,
69 pub created_at: i64,
70}
71
72fn generate_token() -> String {
76 let rng = SystemRandom::new();
77 let mut bytes = [0u8; 32];
78 rng.fill(&mut bytes).expect("system RNG unavailable");
79 BASE64_URL_SAFE_NO_PAD.encode(bytes)
80}
81
82macro_rules! authz_columns {
88 () => {
89 "id, order_id, identifier, status, expires, created_at"
90 };
91}
92
93impl Authorization {
94 fn from_row(row: SqliteRow) -> Result<Self, sqlx::Error> {
95 let identifier_json: String = row.try_get("identifier")?;
96 let identifier: Identifier =
97 serde_json::from_str(&identifier_json).map_err(|e| sqlx::Error::Decode(Box::new(e)))?;
98
99 Ok(Authorization {
100 id: row.try_get("id")?,
101 order_id: row.try_get("order_id")?,
102 identifier,
103 status: status::from_column(row.try_get::<&str, _>("status")?)?,
104 expires: row.try_get("expires")?,
105 created_at: row.try_get("created_at")?,
106 })
107 }
108
109 pub(crate) fn new(order_id: &str, identifier: Identifier, expires: i64) -> Authorization {
112 Authorization {
113 id: Uuid::new_v4().to_string(),
114 order_id: order_id.to_string(),
115 identifier,
116 status: AuthzStatus::Pending,
117 expires,
118 created_at: now_secs(),
119 }
120 }
121
122 pub(crate) async fn insert<'e, E>(&self, executor: E) -> Result<(), sqlx::Error>
125 where
126 E: sqlx::Executor<'e, Database = sqlx::Sqlite>,
127 {
128 let identifier_json = serde_json::to_string(&self.identifier)
130 .map_err(|e| sqlx::Error::Encode(Box::new(e)))?;
131
132 debug!(event = "db_authz_create_started", outcome = "progress", authz_id = ?self.id, order_id = ?self.order_id);
133 sqlx::query(
134 "INSERT INTO authorizations (id, order_id, identifier, status, expires, created_at) \
135 VALUES (?, ?, ?, ?, ?, ?);",
136 )
137 .bind(&self.id)
138 .bind(&self.order_id)
139 .bind(identifier_json)
140 .bind(self.status.as_str())
141 .bind(self.expires)
142 .bind(self.created_at)
143 .execute(executor)
144 .await?;
145
146 info!(event = "db_authz_created", outcome = "success", authz_id = ?self.id, order_id = ?self.order_id);
147 Ok(())
148 }
149
150 pub async fn create(
152 order_id: &str,
153 identifier: Identifier,
154 expires: i64,
155 database: &Database,
156 ) -> Result<Authorization, sqlx::Error> {
157 let authz = Authorization::new(order_id, identifier, expires);
158 authz.insert(&database.pool).await?;
159 Ok(authz)
160 }
161
162 pub async fn find_by_id(
163 id: &str,
164 database: &Database,
165 ) -> Result<Option<Authorization>, sqlx::Error> {
166 debug!(event = "db_authz_find_by_id_started", outcome = "progress", authz_id = ?id);
167 let row = sqlx::query(concat!(
168 "SELECT ",
169 authz_columns!(),
170 " FROM authorizations WHERE id = ?;"
171 ))
172 .bind(id)
173 .fetch_optional(&database.pool)
174 .await?;
175
176 row.map(Authorization::from_row).transpose()
177 }
178
179 pub async fn find_by_order(
182 order_id: &str,
183 database: &Database,
184 ) -> Result<Vec<Authorization>, sqlx::Error> {
185 Self::find_by_order_with(order_id, &database.pool).await
186 }
187
188 pub async fn count_by_order(order_id: &str, database: &Database) -> Result<i64, sqlx::Error> {
191 let row = sqlx::query("SELECT COUNT(*) FROM authorizations WHERE order_id = ?;")
192 .bind(order_id)
193 .fetch_one(&database.pool)
194 .await?;
195 row.try_get::<i64, _>(0)
196 }
197
198 pub async fn find_ids_by_orders(
208 order_ids: &[&str],
209 database: &Database,
210 ) -> Result<std::collections::HashMap<String, Vec<String>>, sqlx::Error> {
211 let mut grouped: std::collections::HashMap<String, Vec<String>> =
212 std::collections::HashMap::new();
213 if order_ids.is_empty() {
214 return Ok(grouped);
215 }
216
217 let mut builder =
221 sqlx::QueryBuilder::new("SELECT id, order_id FROM authorizations WHERE order_id IN (");
222 let mut separated = builder.separated(", ");
223 for id in order_ids {
224 separated.push_bind(*id);
225 }
226 builder.push(") ORDER BY created_at ASC;");
227
228 debug!(
229 event = "db_authz_find_ids_by_orders",
230 outcome = "success",
231 orders = order_ids.len()
232 );
233 for row in builder.build().fetch_all(&database.pool).await? {
234 let order_id: String = row.try_get("order_id")?;
235 let id: String = row.try_get("id")?;
236 grouped.entry(order_id).or_default().push(id);
237 }
238 Ok(grouped)
239 }
240
241 pub(crate) async fn find_by_order_with<'e, E>(
250 order_id: &str,
251 executor: E,
252 ) -> Result<Vec<Authorization>, sqlx::Error>
253 where
254 E: sqlx::Executor<'e, Database = sqlx::Sqlite>,
255 {
256 debug!(event = "db_authz_find_by_order_started", outcome = "progress", order_id = ?order_id);
257 let rows = sqlx::query(concat!(
258 "SELECT ",
259 authz_columns!(),
260 " FROM authorizations WHERE order_id = ? ORDER BY created_at ASC;"
261 ))
262 .bind(order_id)
263 .fetch_all(executor)
264 .await?;
265
266 rows.into_iter().map(Authorization::from_row).collect()
267 }
268
269 pub(crate) async fn set_valid<'e, E>(id: &str, executor: E) -> Result<(), sqlx::Error>
277 where
278 E: sqlx::Executor<'e, Database = sqlx::Sqlite>,
279 {
280 sqlx::query("UPDATE authorizations SET status = 'valid' WHERE id = ?;")
281 .bind(id)
282 .execute(executor)
283 .await?;
284 Ok(())
285 }
286
287 pub(crate) async fn set_invalid<'e, E>(id: &str, executor: E) -> Result<(), sqlx::Error>
289 where
290 E: sqlx::Executor<'e, Database = sqlx::Sqlite>,
291 {
292 sqlx::query("UPDATE authorizations SET status = 'invalid' WHERE id = ?;")
293 .bind(id)
294 .execute(executor)
295 .await?;
296 Ok(())
297 }
298
299 pub(crate) async fn set_deactivated<'e, E>(id: &str, executor: E) -> Result<(), sqlx::Error>
310 where
311 E: sqlx::Executor<'e, Database = sqlx::Sqlite>,
312 {
313 sqlx::query("UPDATE authorizations SET status = 'deactivated' WHERE id = ?;")
314 .bind(id)
315 .execute(executor)
316 .await?;
317 Ok(())
318 }
319
320 pub async fn mark_valid(&mut self, database: &Database) -> Result<(), sqlx::Error> {
323 debug!(event = "db_authz_mark_valid_started", outcome = "progress", authz_id = ?self.id);
324 Self::set_valid(&self.id, &database.pool).await?;
325
326 self.status = AuthzStatus::Valid;
327 info!(event = "db_authz_marked_valid", outcome = "success", authz_id = ?self.id);
328 Ok(())
329 }
330
331 pub async fn mark_invalid(&mut self, database: &Database) -> Result<(), sqlx::Error> {
338 debug!(event = "db_authz_mark_invalid_started", outcome = "progress", authz_id = ?self.id);
339 Self::set_invalid(&self.id, &database.pool).await?;
340
341 self.status = AuthzStatus::Invalid;
342 info!(event = "db_authz_marked_invalid", outcome = "failure", authz_id = ?self.id);
343 Ok(())
344 }
345
346 #[must_use]
351 pub fn is_wildcard(&self) -> bool {
352 self.identifier.value.starts_with("*.")
353 }
354
355 #[must_use]
361 pub fn base_identifier(&self) -> &str {
362 self.identifier
363 .value
364 .strip_prefix("*.")
365 .unwrap_or(&self.identifier.value)
366 }
367
368 #[must_use]
372 pub fn to_json(&self, base_url: &str, challenges: &[Challenge]) -> Value {
373 let mut object = serde_json::Map::new();
374 object.insert(
378 "identifier".to_string(),
379 serde_json::to_value(Identifier::new(
380 self.identifier.typ.clone(),
381 self.base_identifier().to_string(),
382 ))
383 .expect("Identifier is always serializable"),
384 );
385 object.insert(
386 "status".to_string(),
387 Value::String(self.status.as_str().to_string()),
388 );
389 object.insert("expires".to_string(), Value::String(rfc3339(self.expires)));
390 let challenges: Vec<Value> = challenges.iter().map(|c| c.to_json(base_url)).collect();
391 object.insert("challenges".to_string(), Value::Array(challenges));
392 if self.is_wildcard() {
393 object.insert("wildcard".to_string(), Value::Bool(true));
394 }
395 Value::Object(object)
396 }
397}
398
399macro_rules! challenge_columns {
405 () => {
406 "id, authz_id, type, token, status, validated, error, created_at"
407 };
408}
409
410impl Challenge {
411 fn from_row(row: SqliteRow) -> Result<Self, sqlx::Error> {
412 let error_json: Option<String> = row.try_get("error")?;
413 let error = error_json
414 .map(|json| serde_json::from_str(&json))
415 .transpose()
416 .map_err(|e| sqlx::Error::Decode(Box::new(e)))?;
417
418 Ok(Challenge {
419 id: row.try_get("id")?,
420 authz_id: row.try_get("authz_id")?,
421 typ: row.try_get("type")?,
422 token: row.try_get("token")?,
423 status: status::from_column(row.try_get::<&str, _>("status")?)?,
424 validated: row.try_get("validated")?,
425 error,
426 created_at: row.try_get("created_at")?,
427 })
428 }
429
430 pub(crate) fn new(authz_id: &str, typ: &str) -> Challenge {
437 Challenge {
438 id: Uuid::new_v4().to_string(),
439 authz_id: authz_id.to_string(),
440 typ: typ.to_string(),
441 token: generate_token(),
442 status: ChallengeStatus::Pending,
443 validated: None,
444 error: None,
445 created_at: now_secs(),
446 }
447 }
448
449 pub(crate) async fn insert<'e, E>(&self, executor: E) -> Result<(), sqlx::Error>
452 where
453 E: sqlx::Executor<'e, Database = sqlx::Sqlite>,
454 {
455 debug!(event = "db_challenge_create_started", outcome = "progress", challenge_id = ?self.id, authz_id = ?self.authz_id);
456 sqlx::query(
457 "INSERT INTO challenges (id, authz_id, type, token, status, validated, created_at) \
458 VALUES (?, ?, ?, ?, ?, NULL, ?);",
459 )
460 .bind(&self.id)
461 .bind(&self.authz_id)
462 .bind(&self.typ)
463 .bind(&self.token)
464 .bind(self.status.as_str())
465 .bind(self.created_at)
466 .execute(executor)
467 .await?;
468
469 info!(event = "db_challenge_created", outcome = "success", challenge_id = ?self.id, authz_id = ?self.authz_id);
470 Ok(())
471 }
472
473 pub async fn create(
476 authz_id: &str,
477 typ: &str,
478 database: &Database,
479 ) -> Result<Challenge, sqlx::Error> {
480 let challenge = Challenge::new(authz_id, typ);
481 challenge.insert(&database.pool).await?;
482 Ok(challenge)
483 }
484
485 pub async fn find_by_id(
486 id: &str,
487 database: &Database,
488 ) -> Result<Option<Challenge>, sqlx::Error> {
489 debug!(event = "db_challenge_find_by_id_started", outcome = "progress", challenge_id = ?id);
490 let row = sqlx::query(concat!(
491 "SELECT ",
492 challenge_columns!(),
493 " FROM challenges WHERE id = ?;"
494 ))
495 .bind(id)
496 .fetch_optional(&database.pool)
497 .await?;
498
499 row.map(Challenge::from_row).transpose()
500 }
501
502 pub async fn find_by_authz(
505 authz_id: &str,
506 database: &Database,
507 ) -> Result<Vec<Challenge>, sqlx::Error> {
508 debug!(event = "db_challenge_find_by_authz_started", outcome = "progress", authz_id = ?authz_id);
509 let rows = sqlx::query(concat!(
510 "SELECT ",
511 challenge_columns!(),
512 " FROM challenges WHERE authz_id = ? ORDER BY created_at ASC;"
513 ))
514 .bind(authz_id)
515 .fetch_all(&database.pool)
516 .await?;
517
518 rows.into_iter().map(Challenge::from_row).collect()
519 }
520
521 pub(crate) async fn set_valid<'e, E>(
530 id: &str,
531 validated: i64,
532 executor: E,
533 ) -> Result<(), sqlx::Error>
534 where
535 E: sqlx::Executor<'e, Database = sqlx::Sqlite>,
536 {
537 sqlx::query("UPDATE challenges SET status = 'valid', validated = ? WHERE id = ?;")
538 .bind(validated)
539 .bind(id)
540 .execute(executor)
541 .await?;
542 Ok(())
543 }
544
545 pub(crate) async fn set_invalid<'e, E>(
547 id: &str,
548 error: &Value,
549 executor: E,
550 ) -> Result<(), sqlx::Error>
551 where
552 E: sqlx::Executor<'e, Database = sqlx::Sqlite>,
553 {
554 let error_json =
555 serde_json::to_string(error).map_err(|e| sqlx::Error::Encode(Box::new(e)))?;
556 sqlx::query("UPDATE challenges SET status = 'invalid', error = ? WHERE id = ?;")
557 .bind(error_json)
558 .bind(id)
559 .execute(executor)
560 .await?;
561 Ok(())
562 }
563
564 pub async fn mark_valid(&mut self, database: &Database) -> Result<(), sqlx::Error> {
565 let validated = now_secs();
566 debug!(event = "db_challenge_mark_valid_started", outcome = "progress", challenge_id = ?self.id);
567 Self::set_valid(&self.id, validated, &database.pool).await?;
568
569 self.status = ChallengeStatus::Valid;
570 self.validated = Some(validated);
571 info!(event = "db_challenge_marked_valid", outcome = "success", challenge_id = ?self.id);
572 Ok(())
573 }
574
575 pub async fn mark_invalid(
582 &mut self,
583 error: Value,
584 database: &Database,
585 ) -> Result<(), sqlx::Error> {
586 debug!(event = "db_challenge_mark_invalid_started", outcome = "progress", challenge_id = ?self.id);
587 Self::set_invalid(&self.id, &error, &database.pool).await?;
588
589 self.status = ChallengeStatus::Invalid;
590 self.error = Some(error);
591 info!(event = "db_challenge_marked_invalid", outcome = "failure", challenge_id = ?self.id);
592 Ok(())
593 }
594
595 #[must_use]
598 pub fn to_json(&self, base_url: &str) -> Value {
599 let mut object = serde_json::Map::new();
600 object.insert("type".to_string(), Value::String(self.typ.clone()));
601 object.insert(
602 "url".to_string(),
603 Value::String(format!("{base_url}/chall/{}", self.id)),
604 );
605 object.insert(
606 "status".to_string(),
607 Value::String(self.status.as_str().to_string()),
608 );
609 object.insert("token".to_string(), Value::String(self.token.clone()));
610 if let Some(validated) = self.validated {
611 object.insert("validated".to_string(), Value::String(rfc3339(validated)));
612 }
613 if let Some(error) = &self.error {
614 object.insert("error".to_string(), error.clone());
615 }
616 Value::Object(object)
617 }
618}
619
620#[cfg(test)]
621mod tests {
622
623 use super::*;
624 use crate::audit::ClientContext;
625 use crate::sqlite::account::Account;
626 use crate::sqlite::order::Order;
627 use crate::sqlite::status::OrderStatus;
628 use crate::testutil::account_id;
629 use std::sync::Arc;
630
631 #[tokio::test]
633 async fn ids_for_several_orders_come_back_grouped_in_one_query() {
634 let db = Arc::new(Database::connect_in_memory().await.unwrap());
635 let account = account_id(&db).await;
636
637 let mut expected = Vec::new();
638 for name in ["a.example.com", "b.example.com"] {
639 let order = Order::create(
640 "default",
641 &account,
642 vec![Identifier::dns(name)],
643 now_secs() + 3600,
644 None,
645 None,
646 &db,
647 )
648 .await
649 .unwrap();
650 let first =
651 Authorization::create(&order.id, Identifier::dns(name), now_secs() + 3600, &db)
652 .await
653 .unwrap();
654 expected.push((order.id, first.id));
655 }
656
657 let ids: Vec<&str> = expected.iter().map(|(o, _)| o.as_str()).collect();
658 let grouped = Authorization::find_ids_by_orders(&ids, &db).await.unwrap();
659 assert_eq!(grouped.len(), 2);
660 for (order_id, authz_id) in &expected {
661 assert_eq!(grouped[order_id], vec![authz_id.clone()]);
662 }
663
664 let grouped = Authorization::find_ids_by_orders(&["nope"], &db)
667 .await
668 .unwrap();
669 assert!(grouped.is_empty());
670
671 assert!(
673 Authorization::find_ids_by_orders(&[], &db)
674 .await
675 .unwrap()
676 .is_empty()
677 );
678 }
679
680 async fn order_id(db: &Arc<Database>) -> String {
683 let (account, _) = Account::find_or_create(
684 "default",
685 &[1u8, 2, 3],
686 vec![],
687 &ClientContext::default(),
688 db,
689 )
690 .await
691 .unwrap();
692 let order = Order::create(
693 "default",
694 &account.id,
695 vec![Identifier::dns("example.com")],
696 now_secs() + 3600,
697 None,
698 None,
699 db,
700 )
701 .await
702 .unwrap();
703 order.id
704 }
705
706 #[tokio::test]
715 async fn the_validation_transitions_roll_back_together() {
716 let db = Arc::new(Database::connect_in_memory().await.unwrap());
717 let oid = order_id(&db).await;
718 let authz =
719 Authorization::create(&oid, Identifier::dns("example.com"), now_secs() + 3600, &db)
720 .await
721 .unwrap();
722 let challenge = Challenge::create(&authz.id, "http-01", &db).await.unwrap();
723
724 let mut tx = db.pool.begin().await.unwrap();
727 Challenge::set_valid(&challenge.id, now_secs(), &mut *tx)
728 .await
729 .unwrap();
730 Authorization::set_valid(&authz.id, &mut *tx).await.unwrap();
731 Order::set_ready(&oid, &mut *tx).await.unwrap();
732 tx.rollback().await.unwrap();
733
734 let reloaded_authz = Authorization::find_by_id(&authz.id, &db)
735 .await
736 .unwrap()
737 .unwrap();
738 let reloaded_challenge = Challenge::find_by_id(&challenge.id, &db)
739 .await
740 .unwrap()
741 .unwrap();
742 let reloaded_order = Order::find_by_id(&oid, &db).await.unwrap().unwrap();
743
744 assert_eq!(reloaded_challenge.status, ChallengeStatus::Pending);
745 assert_eq!(reloaded_authz.status, AuthzStatus::Pending);
746 assert_eq!(reloaded_order.status, OrderStatus::Pending);
747 }
748
749 #[tokio::test]
752 async fn the_validation_transitions_commit_together() {
753 let db = Arc::new(Database::connect_in_memory().await.unwrap());
754 let oid = order_id(&db).await;
755 let authz =
756 Authorization::create(&oid, Identifier::dns("example.com"), now_secs() + 3600, &db)
757 .await
758 .unwrap();
759 let challenge = Challenge::create(&authz.id, "http-01", &db).await.unwrap();
760
761 let mut tx = db.pool.begin().await.unwrap();
762 Challenge::set_valid(&challenge.id, now_secs(), &mut *tx)
763 .await
764 .unwrap();
765 Authorization::set_valid(&authz.id, &mut *tx).await.unwrap();
766 Order::set_ready(&oid, &mut *tx).await.unwrap();
767 tx.commit().await.unwrap();
768
769 assert_eq!(
770 Challenge::find_by_id(&challenge.id, &db)
771 .await
772 .unwrap()
773 .unwrap()
774 .status,
775 ChallengeStatus::Valid
776 );
777 assert_eq!(
778 Authorization::find_by_id(&authz.id, &db)
779 .await
780 .unwrap()
781 .unwrap()
782 .status,
783 AuthzStatus::Valid
784 );
785 assert_eq!(
786 Order::find_by_id(&oid, &db).await.unwrap().unwrap().status,
787 OrderStatus::Ready
788 );
789 }
790
791 #[tokio::test]
792 async fn authz_create_find_round_trip() {
793 let db = Arc::new(Database::connect_in_memory().await.unwrap());
794 let oid = order_id(&db).await;
795
796 let authz =
797 Authorization::create(&oid, Identifier::dns("example.com"), now_secs() + 3600, &db)
798 .await
799 .unwrap();
800 assert_eq!(authz.status, AuthzStatus::Pending);
801
802 let by_id = Authorization::find_by_id(&authz.id, &db)
803 .await
804 .unwrap()
805 .unwrap();
806 assert_eq!(by_id.identifier, Identifier::dns("example.com"));
807 assert_eq!(by_id.order_id, oid);
808
809 let by_order = Authorization::find_by_order(&oid, &db).await.unwrap();
810 assert_eq!(by_order.len(), 1);
811 }
812
813 #[tokio::test]
814 async fn authz_mark_valid_persists_and_syncs() {
815 let db = Arc::new(Database::connect_in_memory().await.unwrap());
816 let oid = order_id(&db).await;
817
818 let mut authz =
819 Authorization::create(&oid, Identifier::dns("example.com"), now_secs() + 3600, &db)
820 .await
821 .unwrap();
822 authz.mark_valid(&db).await.unwrap();
823
824 assert_eq!(authz.status, AuthzStatus::Valid);
825 let reloaded = Authorization::find_by_id(&authz.id, &db)
826 .await
827 .unwrap()
828 .unwrap();
829 assert_eq!(reloaded.status, AuthzStatus::Valid);
830 }
831
832 #[tokio::test]
833 async fn authz_to_json_shape() {
834 let db = Arc::new(Database::connect_in_memory().await.unwrap());
835 let oid = order_id(&db).await;
836
837 let authz =
838 Authorization::create(&oid, Identifier::dns("example.com"), now_secs() + 3600, &db)
839 .await
840 .unwrap();
841 let challenge = Challenge::create(&authz.id, "http-01", &db).await.unwrap();
842
843 let json = authz.to_json("http://localhost:3000", std::slice::from_ref(&challenge));
844 assert_eq!(json["status"], "pending");
845 assert_eq!(
846 json["identifier"],
847 serde_json::json!({"type":"dns","value":"example.com"})
848 );
849 assert!(json["expires"].as_str().unwrap().ends_with('Z'));
850 assert_eq!(json["challenges"].as_array().unwrap().len(), 1);
851 assert_eq!(json["challenges"][0]["type"], "http-01");
852 }
853
854 #[tokio::test]
855 async fn challenge_create_find_round_trip() {
856 let db = Arc::new(Database::connect_in_memory().await.unwrap());
857 let oid = order_id(&db).await;
858 let authz =
859 Authorization::create(&oid, Identifier::dns("example.com"), now_secs() + 3600, &db)
860 .await
861 .unwrap();
862
863 let challenge = Challenge::create(&authz.id, "http-01", &db).await.unwrap();
864 assert_eq!(challenge.typ, "http-01");
865 assert_eq!(challenge.status, ChallengeStatus::Pending);
866 assert!(!challenge.token.is_empty());
867 assert!(challenge.validated.is_none());
868
869 let by_id = Challenge::find_by_id(&challenge.id, &db)
870 .await
871 .unwrap()
872 .unwrap();
873 assert_eq!(by_id.token, challenge.token);
874
875 let by_authz = Challenge::find_by_authz(&authz.id, &db).await.unwrap();
876 assert_eq!(by_authz.len(), 1);
877 }
878
879 #[tokio::test]
880 async fn challenge_mark_valid_persists_and_syncs() {
881 let db = Arc::new(Database::connect_in_memory().await.unwrap());
882 let oid = order_id(&db).await;
883 let authz =
884 Authorization::create(&oid, Identifier::dns("example.com"), now_secs() + 3600, &db)
885 .await
886 .unwrap();
887
888 let mut challenge = Challenge::create(&authz.id, "http-01", &db).await.unwrap();
889 challenge.mark_valid(&db).await.unwrap();
890
891 assert_eq!(challenge.status, ChallengeStatus::Valid);
892 assert!(challenge.validated.is_some());
893
894 let reloaded = Challenge::find_by_id(&challenge.id, &db)
895 .await
896 .unwrap()
897 .unwrap();
898 assert_eq!(reloaded.status, ChallengeStatus::Valid);
899 let json = reloaded.to_json("http://localhost:3000");
900 assert_eq!(json["status"], "valid");
901 assert_eq!(
902 json["url"],
903 format!("http://localhost:3000/chall/{}", challenge.id)
904 );
905 assert!(json["validated"].as_str().unwrap().ends_with('Z'));
906 }
907
908 #[tokio::test]
909 async fn challenge_mark_invalid_persists_the_problem_document() {
910 let db = Arc::new(Database::connect_in_memory().await.unwrap());
911 let oid = order_id(&db).await;
912 let authz =
913 Authorization::create(&oid, Identifier::dns("example.com"), now_secs() + 3600, &db)
914 .await
915 .unwrap();
916
917 let mut challenge = Challenge::create(&authz.id, "http-01", &db).await.unwrap();
918 let problem = serde_json::json!({
919 "type": "urn:ietf:params:acme:error:incorrectResponse",
920 "detail": "response body does not match the key authorization",
921 "status": 403,
922 });
923 challenge.mark_invalid(problem.clone(), &db).await.unwrap();
924
925 assert_eq!(challenge.status, ChallengeStatus::Invalid);
926 assert_eq!(challenge.error.as_ref(), Some(&problem));
927 assert!(challenge.validated.is_none());
929
930 let reloaded = Challenge::find_by_id(&challenge.id, &db)
931 .await
932 .unwrap()
933 .unwrap();
934 assert_eq!(reloaded.status, ChallengeStatus::Invalid);
935 assert_eq!(reloaded.error.as_ref(), Some(&problem));
936 let json = reloaded.to_json("http://localhost:3000");
937 assert_eq!(json["error"], problem);
938 assert!(json.get("validated").is_none());
939 }
940
941 #[tokio::test]
942 async fn authz_mark_invalid_persists_and_syncs() {
943 let db = Arc::new(Database::connect_in_memory().await.unwrap());
944 let oid = order_id(&db).await;
945
946 let mut authz =
947 Authorization::create(&oid, Identifier::dns("example.com"), now_secs() + 3600, &db)
948 .await
949 .unwrap();
950 authz.mark_invalid(&db).await.unwrap();
951
952 assert_eq!(authz.status, AuthzStatus::Invalid);
953 let reloaded = Authorization::find_by_id(&authz.id, &db)
954 .await
955 .unwrap()
956 .unwrap();
957 assert_eq!(reloaded.status, AuthzStatus::Invalid);
958 }
959
960 #[tokio::test]
963 async fn an_authorization_holds_one_challenge_per_type() {
964 let db = Arc::new(Database::connect_in_memory().await.unwrap());
965 let oid = order_id(&db).await;
966 let authz =
967 Authorization::create(&oid, Identifier::dns("example.com"), now_secs() + 3600, &db)
968 .await
969 .unwrap();
970
971 let http = Challenge::create(&authz.id, "http-01", &db).await.unwrap();
972 let dns_01 = Challenge::create(&authz.id, "dns-01", &db).await.unwrap();
973 Challenge::create(&authz.id, "tls-alpn-01", &db)
974 .await
975 .unwrap();
976
977 assert_ne!(http.token, dns_01.token);
979
980 let challenges = Challenge::find_by_authz(&authz.id, &db).await.unwrap();
981 assert_eq!(challenges.len(), 3);
982
983 assert!(Challenge::create(&authz.id, "http-01", &db).await.is_err());
985 }
986
987 #[tokio::test]
991 async fn a_wildcard_authorization_stores_the_prefix_and_renders_the_base_name() {
992 let db = Arc::new(Database::connect_in_memory().await.unwrap());
993 let oid = order_id(&db).await;
994
995 let authz = Authorization::create(
996 &oid,
997 Identifier::dns("*.example.com"),
998 now_secs() + 3600,
999 &db,
1000 )
1001 .await
1002 .unwrap();
1003 let challenge = Challenge::create(&authz.id, "dns-01", &db).await.unwrap();
1004
1005 let reloaded = Authorization::find_by_id(&authz.id, &db)
1008 .await
1009 .unwrap()
1010 .unwrap();
1011 assert_eq!(reloaded.identifier.value, "*.example.com");
1012 assert!(reloaded.is_wildcard());
1013 assert_eq!(reloaded.base_identifier(), "example.com");
1014
1015 let json = reloaded.to_json("http://localhost:3000", std::slice::from_ref(&challenge));
1016 assert_eq!(
1017 json["identifier"],
1018 serde_json::json!({"type":"dns","value":"example.com"})
1019 );
1020 assert_eq!(json["wildcard"], true);
1021 assert_eq!(json["challenges"][0]["type"], "dns-01");
1022 }
1023
1024 #[tokio::test]
1027 async fn a_plain_authorization_has_no_wildcard_member() {
1028 let db = Arc::new(Database::connect_in_memory().await.unwrap());
1029 let oid = order_id(&db).await;
1030
1031 let authz =
1032 Authorization::create(&oid, Identifier::dns("example.com"), now_secs() + 3600, &db)
1033 .await
1034 .unwrap();
1035 assert!(!authz.is_wildcard());
1036 assert_eq!(authz.base_identifier(), "example.com");
1037 assert!(
1038 authz
1039 .to_json("http://localhost:3000", &[])
1040 .get("wildcard")
1041 .is_none()
1042 );
1043 }
1044
1045 #[tokio::test]
1046 async fn absent_lookups_return_none() {
1047 let db = Arc::new(Database::connect_in_memory().await.unwrap());
1048 assert!(
1049 Authorization::find_by_id("nope", &db)
1050 .await
1051 .unwrap()
1052 .is_none()
1053 );
1054 assert!(Challenge::find_by_id("nope", &db).await.unwrap().is_none());
1055 }
1056}