1use fission_core::{
2 PasskeyAuthenticationRequest, PasskeyAuthenticationResult, PasskeyAvailability, PasskeyError,
3 PasskeyRegistrationRequest, PasskeyRegistrationResult, AUTHENTICATE_PASSKEY,
4 CANCEL_PASSKEY_OPERATION, GET_PASSKEY_AVAILABILITY, REGISTER_PASSKEY,
5};
6use fission_shell::async_host::AsyncRegistry;
7use std::sync::Arc;
8
9pub trait PasskeyHost: Send + Sync + 'static {
11 fn availability(&self) -> Result<PasskeyAvailability, PasskeyError>;
13 fn register(
20 &self,
21 request: PasskeyRegistrationRequest,
22 ) -> Result<PasskeyRegistrationResult, PasskeyError>;
23 fn authenticate(
29 &self,
30 request: PasskeyAuthenticationRequest,
31 ) -> Result<PasskeyAuthenticationResult, PasskeyError>;
32 fn cancel(&self) -> Result<(), PasskeyError>;
34}
35
36#[derive(Debug, Default)]
38pub struct UnsupportedPasskeyHost;
39
40impl PasskeyHost for UnsupportedPasskeyHost {
41 fn availability(&self) -> Result<PasskeyAvailability, PasskeyError> {
42 Ok(PasskeyAvailability {
43 reason: Some("passkeys are not supported by this host".into()),
44 ..Default::default()
45 })
46 }
47
48 fn register(
49 &self,
50 _request: PasskeyRegistrationRequest,
51 ) -> Result<PasskeyRegistrationResult, PasskeyError> {
52 Err(PasskeyError::unsupported("register"))
53 }
54
55 fn authenticate(
56 &self,
57 _request: PasskeyAuthenticationRequest,
58 ) -> Result<PasskeyAuthenticationResult, PasskeyError> {
59 Err(PasskeyError::unsupported("authenticate"))
60 }
61
62 fn cancel(&self) -> Result<(), PasskeyError> {
63 Err(PasskeyError::unsupported("cancel"))
64 }
65}
66
67#[derive(Debug, Clone)]
69pub struct MemoryPasskeyHost {
70 availability: PasskeyAvailability,
71 registration_result: PasskeyRegistrationResult,
72 authentication_result: PasskeyAuthenticationResult,
73}
74
75impl MemoryPasskeyHost {
76 pub fn new(
77 availability: PasskeyAvailability,
78 registration_result: PasskeyRegistrationResult,
79 authentication_result: PasskeyAuthenticationResult,
80 ) -> Self {
81 Self {
82 availability,
83 registration_result,
84 authentication_result,
85 }
86 }
87}
88
89impl Default for MemoryPasskeyHost {
90 fn default() -> Self {
91 Self {
92 availability: PasskeyAvailability {
93 supported: true,
94 secure_context: true,
95 platform_authenticator_available: true,
96 conditional_ui_available: true,
97 cross_platform_authenticator_available: true,
98 reason: None,
99 },
100 registration_result: PasskeyRegistrationResult {
101 credential_id: vec![1, 2, 3],
102 raw_id: vec![1, 2, 3],
103 client_data_json: br#"{"type":"webauthn.create"}"#.to_vec(),
104 attestation_object: vec![4, 5, 6],
105 authenticator_attachment: None,
106 transports: Vec::new(),
107 },
108 authentication_result: PasskeyAuthenticationResult {
109 credential_id: vec![1, 2, 3],
110 raw_id: vec![1, 2, 3],
111 user_handle: Some(vec![9]),
112 client_data_json: br#"{"type":"webauthn.get"}"#.to_vec(),
113 authenticator_data: vec![4],
114 signature: vec![5, 6],
115 },
116 }
117 }
118}
119
120impl PasskeyHost for MemoryPasskeyHost {
121 fn availability(&self) -> Result<PasskeyAvailability, PasskeyError> {
122 Ok(self.availability.clone())
123 }
124
125 fn register(
126 &self,
127 _request: PasskeyRegistrationRequest,
128 ) -> Result<PasskeyRegistrationResult, PasskeyError> {
129 Ok(self.registration_result.clone())
130 }
131
132 fn authenticate(
133 &self,
134 _request: PasskeyAuthenticationRequest,
135 ) -> Result<PasskeyAuthenticationResult, PasskeyError> {
136 Ok(self.authentication_result.clone())
137 }
138
139 fn cancel(&self) -> Result<(), PasskeyError> {
140 Ok(())
141 }
142}
143
144pub(crate) fn register_passkey_capabilities(
145 async_registry: &mut AsyncRegistry,
146 host: Arc<dyn PasskeyHost>,
147) {
148 let availability_host = host.clone();
149 async_registry.register_operation_capability(GET_PASSKEY_AVAILABILITY, move |(), _| {
150 let host = availability_host.clone();
151 async move { host.availability() }
152 });
153
154 let register_host = host.clone();
155 async_registry.register_operation_capability(REGISTER_PASSKEY, move |request, _| {
156 let host = register_host.clone();
157 async move { host.register(request) }
158 });
159
160 let authenticate_host = host.clone();
161 async_registry.register_operation_capability(AUTHENTICATE_PASSKEY, move |request, _| {
162 let host = authenticate_host.clone();
163 async move { host.authenticate(request) }
164 });
165
166 async_registry.register_operation_capability(CANCEL_PASSKEY_OPERATION, move |(), _| {
167 let host = host.clone();
168 async move { host.cancel() }
169 });
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use fission_core::{
176 PasskeyAlgorithm, PasskeyAttestationConveyance, PasskeyAuthenticationRequest,
177 PasskeyMediation, PasskeyRegistrationRequest, PasskeyRelyingParty, PasskeyUser,
178 PasskeyUserVerification,
179 };
180
181 #[test]
182 fn unsupported_host_reports_unavailable() {
183 let host = UnsupportedPasskeyHost;
184 let availability = host.availability().unwrap();
185
186 assert!(!availability.supported);
187 assert!(host
188 .authenticate(PasskeyAuthenticationRequest {
189 relying_party_id: "example.com".into(),
190 challenge: vec![1],
191 allow_credentials: Vec::new(),
192 user_verification: PasskeyUserVerification::Preferred,
193 mediation: PasskeyMediation::PlatformDefault,
194 timeout_ms: None,
195 })
196 .is_err());
197 }
198
199 #[test]
200 fn memory_host_registers_and_authenticates() {
201 let host = MemoryPasskeyHost::default();
202 let availability = host.availability().unwrap();
203 assert!(availability.supported);
204
205 let registration = host
206 .register(PasskeyRegistrationRequest {
207 relying_party: PasskeyRelyingParty::new("example.com", "Example"),
208 user: PasskeyUser::new(vec![1], "ada@example.com", "Ada"),
209 challenge: vec![2],
210 pub_key_algorithms: vec![PasskeyAlgorithm::ES256],
211 timeout_ms: None,
212 attestation: PasskeyAttestationConveyance::None,
213 authenticator_selection: None,
214 exclude_credentials: Vec::new(),
215 })
216 .unwrap();
217 assert_eq!(registration.credential_id, vec![1, 2, 3]);
218
219 let authentication = host
220 .authenticate(PasskeyAuthenticationRequest {
221 relying_party_id: "example.com".into(),
222 challenge: vec![3],
223 allow_credentials: Vec::new(),
224 user_verification: PasskeyUserVerification::Preferred,
225 mediation: PasskeyMediation::PlatformDefault,
226 timeout_ms: None,
227 })
228 .unwrap();
229 assert_eq!(authentication.credential_id, vec![1, 2, 3]);
230 }
231}