1use crate::{PrayError, PrayResult};
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::path::Path;
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7pub struct RegistryTrustSettings {
8 #[serde(default)]
9 pub email_confirmation: EmailConfirmationPolicy,
10 #[serde(default)]
11 pub passkeys_enabled: bool,
12 #[serde(default)]
13 pub ssh_keys_enabled: bool,
14 #[serde(default)]
15 pub ssh_agent_signing_enabled: bool,
16}
17
18impl Default for RegistryTrustSettings {
19 fn default() -> Self {
20 Self {
21 email_confirmation: EmailConfirmationPolicy::Required,
22 passkeys_enabled: false,
23 ssh_keys_enabled: false,
24 ssh_agent_signing_enabled: false,
25 }
26 }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
30#[serde(rename_all = "snake_case")]
31pub enum EmailConfirmationPolicy {
32 #[default]
33 Required,
34 Optional,
35 Disabled,
36}
37
38impl RegistryTrustSettings {
39 pub fn email_confirmation_label(&self) -> &'static str {
40 match self.email_confirmation {
41 EmailConfirmationPolicy::Required => "required",
42 EmailConfirmationPolicy::Optional => "optional",
43 EmailConfirmationPolicy::Disabled => "disabled",
44 }
45 }
46
47 pub fn passkeys_label(&self) -> &'static str {
48 if self.passkeys_enabled {
49 "enabled"
50 } else {
51 "disabled"
52 }
53 }
54
55 pub fn ssh_keys_label(&self) -> &'static str {
56 if self.ssh_keys_enabled {
57 "enabled"
58 } else {
59 "disabled"
60 }
61 }
62
63 pub fn ssh_agent_label(&self) -> &'static str {
64 if self.ssh_agent_signing_enabled {
65 "enabled"
66 } else {
67 "disabled"
68 }
69 }
70}
71
72pub fn read_registry_trust_settings(root: &Path) -> PrayResult<RegistryTrustSettings> {
73 let path = root.join("v1/trust.json");
74 let Ok(text) = fs::read_to_string(&path) else {
75 return Ok(RegistryTrustSettings::default());
76 };
77 let settings: RegistryTrustSettings =
78 serde_json::from_str(&text).map_err(|error| PrayError::Parse {
79 kind: "registry trust settings",
80 message: error.to_string(),
81 })?;
82 Ok(settings)
83}
84
85pub fn write_registry_trust_settings(
86 root: &Path,
87 settings: &RegistryTrustSettings,
88) -> PrayResult<()> {
89 let path = root.join("v1/trust.json");
90 if let Some(parent) = path.parent() {
91 fs::create_dir_all(parent)?;
92 }
93 fs::write(
94 path,
95 serde_json::to_string_pretty(settings)
96 .map_err(|error| PrayError::Manifest(error.to_string()))?,
97 )?;
98 Ok(())
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn defaults_require_email_confirmation_and_disable_other_methods() {
107 let settings = RegistryTrustSettings::default();
108 assert_eq!(settings.email_confirmation_label(), "required");
109 assert_eq!(settings.passkeys_label(), "disabled");
110 assert_eq!(settings.ssh_keys_label(), "disabled");
111 assert_eq!(settings.ssh_agent_label(), "disabled");
112 }
113
114 #[test]
115 fn parses_optional_email_confirmation_and_key_methods() {
116 let settings: RegistryTrustSettings = serde_json::from_str(
117 r#"{
118 "email_confirmation": "optional",
119 "passkeys_enabled": true,
120 "ssh_keys_enabled": true,
121 "ssh_agent_signing_enabled": true
122 }"#,
123 )
124 .expect("parse trust settings");
125
126 assert_eq!(
127 settings.email_confirmation,
128 EmailConfirmationPolicy::Optional
129 );
130 assert_eq!(settings.email_confirmation_label(), "optional");
131 assert_eq!(settings.passkeys_label(), "enabled");
132 assert_eq!(settings.ssh_keys_label(), "enabled");
133 assert_eq!(settings.ssh_agent_label(), "enabled");
134 }
135}