1use std::net::IpAddr;
10use std::sync::Arc;
11
12use axum::http::StatusCode;
13use serde::Deserialize;
14use tracing::{error, info, warn};
15use uuid::Uuid;
16
17use super::access::signer_account;
18use super::error::Error;
19use super::rules::validate_contacts;
20use crate::profile::Profile;
21use acme_proxy_core::audit::RequestContext;
22use acme_proxy_core::eab;
23use acme_proxy_core::error::Problem;
24use acme_proxy_core::jws::ProtectedHeader;
25use acme_proxy_core::jws::signature::spki_to_jwk;
26use acme_proxy_core::key_change;
27use acme_proxy_jobs::auditor::Auditor;
28use acme_proxy_jobs::notify::AccountCreatedData;
29use acme_proxy_jobs::notify::AccountDeactivatedData;
30use acme_proxy_jobs::notify::NotifyDispatcher;
31use acme_proxy_jobs::notify::NotifyEvent;
32use acme_proxy_store::account::Account;
33use acme_proxy_store::account::pubkey_fingerprint;
34use acme_proxy_store::db::Database;
35use acme_proxy_store::eab::Eab;
36use acme_proxy_store::order::Order;
37
38#[derive(Debug, Default, Deserialize)]
41#[serde(default)]
42pub struct NewAccountPayload {
43 pub contact: Vec<String>,
44 #[serde(alias = "termsOfServiceAgreed")]
45 pub terms_of_service_agreed: bool,
46 #[serde(alias = "onlyReturnExisting")]
47 pub only_return_existing: bool,
48 #[serde(alias = "externalAccountBinding")]
49 pub external_account_binding: Option<eab::EabJws>,
50}
51
52#[derive(Debug, Default, Deserialize)]
54#[serde(default)]
55pub struct UpdateAccountPayload {
56 pub contact: Option<Vec<String>>,
57 pub status: Option<String>,
58}
59
60pub struct AccountService<'a> {
63 pub database: &'a Arc<Database>,
64 pub audit: &'a Auditor,
65 pub profile: &'a Profile,
66}
67
68impl AccountService<'_> {
69 pub async fn new_account(
77 &self,
78 payload: NewAccountPayload,
79 header: &ProtectedHeader,
80 pubkey: &[u8],
81 client_ip: Option<IpAddr>,
82 request: &RequestContext,
83 ) -> Result<(Account, bool), Error> {
84 let (database, profile, audit) = (self.database, self.profile, self.audit);
85
86 if payload.only_return_existing {
87 let account = Account::find_by_pubkey(&profile.name, pubkey, database)
88 .await
89 .map_err(|error| {
90 error!(event = "account_only_return_existing_lookup_failed", outcome = "failure", error = %error);
94 Problem::server_internal("Account lookup failed")
95 })?
96 .ok_or_else(|| {
97 info!(event = "account_only_return_existing_miss", outcome = "failure");
98 Problem::account_does_not_exist("No account for this key")
99 })?;
100
101 refuse_deactivated(&account, true)?;
102 return Ok((account, false));
103 }
104
105 validate_contacts(&payload.contact)?;
109
110 if !profile.meta.terms_of_service.is_empty() && !payload.terms_of_service_agreed {
116 warn!(event = "account_terms_not_agreed", outcome = "failure");
117 return Err(Error::TermsNotAgreed);
118 }
119
120 let eab_kid = if profile.eab.enabled {
121 Some(
122 verify_eab(
123 payload.external_account_binding.as_ref(),
124 header,
125 &profile.name,
126 database,
127 )
128 .await?,
129 )
130 } else {
131 None
132 };
133
134 let client = audit.client(request).await;
140 let registration = acme_proxy_store::account::Registration {
148 eab_kid,
149 terms_agreed: !profile.meta.terms_of_service.is_empty(),
150 };
151 let (account, created) = Account::find_or_register(
152 &profile.name,
153 pubkey,
154 payload.contact,
155 ®istration,
156 &client,
157 database,
158 )
159 .await
160 .map_err(|error| {
161 error!(event = "account_creation_failed", outcome = "failure", error = %error);
162 Problem::server_internal("Account persistence failed")
163 })?;
164
165 if !created {
168 refuse_deactivated(&account, false)?;
169 }
170
171 if created {
172 profile
173 .notify
174 .dispatch(NotifyEvent::AccountCreated(AccountCreatedData {
175 profile: profile.name.clone(),
176 account_id: account.id.to_string(),
177 contact: account.contact.clone(),
178 client_ip: client_ip
179 .map(|ip| acme_proxy_core::client::canonical(ip).to_string()),
180 }))
181 .await;
182 }
183
184 let status = if created {
185 StatusCode::CREATED
186 } else {
187 StatusCode::OK
188 };
189 if created {
194 info!(event = "account_created", outcome = "success", account_id = %account.id, status = %status);
195 } else {
196 info!(event = "account_found", outcome = "success", account_id = %account.id, status = %status);
197 }
198 Ok((account, created))
199 }
200
201 pub async fn update(
204 &self,
205 id: &str,
206 payload: UpdateAccountPayload,
207 pubkey: &[u8],
208 client_ip: Option<IpAddr>,
209 ) -> Result<Account, Error> {
210 let (database, profile) = (self.database, self.profile);
211
212 let mut account = match Account::find_by_id(&profile.name, id, database).await {
213 Ok(Some(account)) => account,
214 Ok(None) => {
215 warn!(event = "account_not_found", outcome = "failure", account_id = %id);
216 return Err(Problem::account_does_not_exist("Unknown account").into());
217 }
218 Err(error) => {
219 error!(
220 event = "account_update_lookup_failed",
221 outcome = "failure",
222 account_id = %id,
223 error = %error
224 );
225 return Err(Problem::server_internal("Account lookup failed").into());
226 }
227 };
228
229 if account.pubkey != pubkey {
230 warn!(
231 event = "account_key_mismatch",
232 outcome = "failure",
233 account_id = %id,
234 expected_pubkey_fp = %pubkey_fingerprint(&account.pubkey),
235 actual_pubkey_fp = %pubkey_fingerprint(pubkey)
236 );
237 return Err(Problem::unauthorized("Signed by a different account key").into());
238 }
239
240 if account.is_deactivated() {
241 warn!(
242 event = "account_deactivated_modify_refused",
243 outcome = "failure",
244 account_id = %id
245 );
246 return Err(Problem::unauthorized("Account deactivated").into());
247 }
248
249 if let Some(status) = payload.status {
250 if status != acme_proxy_store::account::DEACTIVATED {
251 warn!(event = "account_update_bad_status", outcome = "failure", account_id = %id, status = %status);
252 return Err(Problem::malformed("Only 'deactivated' status is accepted").into());
253 }
254 deactivate(
255 &mut account,
256 database,
257 Some(&profile.notify),
258 client_ip.map(|ip| acme_proxy_core::client::canonical(ip).to_string()),
259 )
260 .await
261 .map_err(|error| {
262 error!(
263 event = "account_deactivation_failed",
264 outcome = "failure",
265 account_id = %id,
266 error = %error
267 );
268 Problem::server_internal("Account update failed")
269 })?;
270 info!(
271 event = "account_deactivated",
272 outcome = "success",
273 account_id = %id
274 );
275 } else if let Some(contact) = payload.contact {
276 update_contact(&mut account, contact, database)
277 .await
278 .map_err(|error| match error {
279 ContactUpdateError::Refused(problem) => problem,
280 ContactUpdateError::Database(error) => {
281 error!(
282 event = "account_contact_update_failed",
283 outcome = "failure",
284 account_id = %id,
285 error = %error
286 );
287 Problem::server_internal("Account update failed")
288 }
289 })?;
290 info!(
291 event = "account_contact_updated",
292 outcome = "success",
293 account_id = %id
294 );
295 }
296
297 info!(
298 event = "account_updated",
299 outcome = "success",
300 account_id = %id
301 );
302 Ok(account)
303 }
304
305 pub async fn key_change(
312 &self,
313 cached: Option<Account>,
314 old_pubkey: &[u8],
315 header: &ProtectedHeader,
316 inner_jws: &key_change::KeyChangeJws,
317 ) -> Result<Account, Error> {
318 let (database, profile) = (self.database, self.profile);
319 let base = &profile.base_url;
320
321 let mut old_account = signer_account(cached, &profile.name, old_pubkey, database).await?;
322
323 let inner_header = key_change::parse_header(inner_jws, &header.url)
324 .map_err(key_change::key_change_problem)?;
325 let new_pubkey = key_change::verify_signature(inner_jws, &inner_header)
326 .map_err(key_change::key_change_problem)?;
327
328 let account_url = format!("{base}/acct/{}", old_account.id);
329 let old_key_jwk = spki_to_jwk(&old_account.pubkey).map_err(|error| {
330 error!(event = "key_change_old_key_decode_failed", outcome = "failure", account_id = %old_account.id, error = %error);
331 Problem::server_internal("Stored account key could not be decoded")
332 })?;
333 key_change::verify_payload(inner_jws, &account_url, &old_key_jwk)
334 .map_err(key_change::key_change_problem)?;
335
336 let conflicting_account = Account::find_by_pubkey(&profile.name, &new_pubkey, database)
337 .await
338 .map_err(|error| {
339 error!(event = "key_change_conflict_lookup_failed", outcome = "failure", account_id = %old_account.id, error = %error);
340 Problem::server_internal("Account lookup failed")
341 })?;
342 if let Some(existing) = conflicting_account {
343 warn!(event = "key_change_conflict", outcome = "failure", account_id = %old_account.id, conflicting_account_id = %existing.id);
344 return Err(Error::KeyChangeConflict {
345 holder: existing.id,
346 });
347 }
348
349 if let Err(error) = old_account.update_pubkey(&new_pubkey, database).await {
350 if acme_proxy_store::account::is_pubkey_conflict(&error)
360 && let Ok(Some(winner)) =
361 Account::find_by_pubkey(&profile.name, &new_pubkey, database).await
362 {
363 warn!(event = "key_change_conflict", outcome = "failure", account_id = %old_account.id, conflicting_account_id = %winner.id);
364 return Err(Error::KeyChangeConflict { holder: winner.id });
365 }
366 error!(event = "key_change_persist_failed", outcome = "failure", account_id = %old_account.id, error = %error);
367 return Err(Problem::server_internal("Account key update failed").into());
368 }
369
370 info!(event = "account_key_changed", outcome = "success", account_id = %old_account.id);
371 Ok(old_account)
372 }
373
374 pub async fn orders(
377 &self,
378 cached: Option<Account>,
379 pubkey: &[u8],
380 id: &str,
381 ) -> Result<Vec<Order>, Error> {
382 let (database, profile) = (self.database, self.profile);
383 let account = signer_account(cached, &profile.name, pubkey, database).await?;
384 if account.id.to_string() != id {
385 warn!(
386 event = "account_orders_ownership_mismatch",
387 outcome = "failure",
388 requested = %id,
389 signer = %account.id
390 );
391 return Err(Problem::unauthorized("Not your account").into());
392 }
393
394 Ok(Order::find_active_by_account(account.id, database)
397 .await
398 .map_err(|error| {
399 error!(
400 event = "account_orders_lookup_failed",
401 outcome = "failure",
402 account_id = %id,
403 error = %error
404 );
405 Problem::server_internal("Order list failed")
406 })?)
407 }
408}
409
410fn refuse_deactivated(account: &Account, only_return_existing: bool) -> Result<(), Problem> {
423 if !account.is_deactivated() {
424 return Ok(());
425 }
426 warn!(
427 event = "account_deactivated_registration_refused",
428 outcome = "failure",
429 account_id = %account.id,
430 only_return_existing = only_return_existing
431 );
432 Err(Problem::unauthorized("Account is deactivated"))
433}
434
435pub async fn verify_eab(
437 eab_jws: Option<&eab::EabJws>,
438 header: &ProtectedHeader,
439 profile: &str,
440 database: &Arc<Database>,
441) -> Result<Uuid, Problem> {
442 let eab_jws = eab_jws.ok_or_else(|| {
443 warn!(event = "eab_required", outcome = "failure", profile);
444 Problem::external_account_required("This server requires External Account Binding")
445 })?;
446
447 let outer_jwk = header.jwk.as_ref().ok_or_else(|| {
448 warn!(event = "eab_missing_jwk", outcome = "failure", profile);
449 Problem::malformed("newAccount requires an embedded jwk for External Account Binding")
450 })?;
451
452 let eab_header = eab::parse_header(eab_jws, &header.url).map_err(eab::eab_problem)?;
453
454 let key = Eab::find_by_kid(&eab_header.kid, profile, database)
455 .await
456 .map_err(|error| {
457 error!(event = "eab_lookup_failed", outcome = "failure", kid = %eab_header.kid, error = %error);
458 Problem::server_internal("External Account Binding lookup failed")
459 })?
460 .filter(Eab::is_active)
461 .ok_or_else(|| {
462 warn!(event = "eab_unknown_or_revoked_kid", outcome = "failure", kid = %eab_header.kid);
463 Problem::unauthorized("Unknown or revoked External Account Binding key")
464 })?;
465
466 eab::verify_payload_and_signature(eab_jws, &key.secret, outer_jwk).map_err(eab::eab_problem)?;
467
468 info!(event = "eab_verified", outcome = "success", kid = %key.kid);
469 Ok(key.kid)
470}
471
472pub async fn deactivate(
480 account: &mut Account,
481 database: &Database,
482 notify: Option<&NotifyDispatcher>,
483 client_ip: Option<String>,
484) -> Result<(), sqlx::Error> {
485 account.deactivate(database).await?;
486 if let Some(dispatcher) = notify {
487 dispatcher
488 .dispatch(NotifyEvent::AccountDeactivated(AccountDeactivatedData {
489 profile: account.profile.clone(),
490 account_id: account.id.to_string(),
491 client_ip,
492 }))
493 .await;
494 }
495 Ok(())
496}
497
498#[derive(Debug, thiserror::Error)]
500pub enum ContactUpdateError {
501 #[error("{0}")]
503 Refused(Problem),
504 #[error("the contact list could not be stored")]
505 Database(#[from] sqlx::Error),
506}
507
508pub async fn update_contact(
513 account: &mut Account,
514 contact: Vec<String>,
515 database: &Database,
516) -> Result<(), ContactUpdateError> {
517 validate_contacts(&contact).map_err(ContactUpdateError::Refused)?;
518 account
519 .update_contact(contact, database)
520 .await
521 .map_err(ContactUpdateError::Database)
522}