pub struct Account {Show 13 fields
pub id: Uuid,
pub profile: String,
pub pubkey: Vec<u8>,
pub contact: Vec<String>,
pub status: String,
pub created_at: i64,
pub eab_kid: Option<Uuid>,
pub terms_of_service_agreed: Option<bool>,
pub created_ip: Option<String>,
pub created_ptr: Option<String>,
pub last_seen_at: Option<i64>,
pub last_seen_ip: Option<String>,
pub last_seen_ptr: Option<String>,
}Expand description
An ACME account (RFC 8555 §7.1.2), keyed by the client’s public key stored as
DER SPKI. contact is persisted as a JSON array of strings.
§ACME Protocol Compliance
This struct represents the account object as defined in RFC 8555:
id: Unique identifier for the account (UUID)pubkey: DER-encoded SPKI public key used for authenticationcontact: Array of contact URIs (email, etc.) for the account holderstatus: Account status (valid, deactivated, etc.)created_at: Timestamp when the account was created
§Storage Details
- The public key is stored in DER SPKI format for consistent hashing and lookup
- Contact information is serialized as JSON for flexible storage
- The ID is generated as a UUID v4 for uniqueness
- Status is tracked to support account lifecycle management
§Methods
find_by_pubkey: Lookup account by public keyfind_by_id: Lookup account by IDfind_or_create: Create new account or return existing one (RFC 8555 §7.3)delete: Hard-delete an account, cascading to its orders (admin CLI)to_json: Convert to RFC 8555 account JSON object format
Fields§
§id: Uuid§profile: StringThe ACME endpoint ([profiles.<name>]) this account was registered at.
Accounts are keyed by (profile, pubkey), so the same client key at two
endpoints is two accounts — see the schema comment in
migrations/20260722210000_add_accounts.sql for why that is a security
property and not just tidiness.
pubkey: Vec<u8>§contact: Vec<String>§status: String§created_at: i64§eab_kid: Option<Uuid>Which EAB credential (if any) created this account – an audit trail
only, set once and never overwritten. See Account::set_eab_kid.
terms_of_service_agreed: Option<bool>Whether this account agreed to the terms of service when it was created
(RFC 8555 §7.3.3). None for an account created at an endpoint that
advertised none — which is not the same as “declined”, and renders as an
absent member rather than false. Set once, at creation; see
Account::set_terms_agreed.
created_ip: Option<String>Where newAccount was called from, and the reverse name that address
had at the time. Traceability only — see the schema comment in
migrations/20260722210000_add_accounts.sql for why nothing ever
compares against these.
created_ptr: Option<String>§last_seen_at: Option<i64>When this key last authenticated a request, and from where. Advanced by
Account::touch under the ACCOUNT_TOUCH_INTERVAL throttle.
last_seen_ip: Option<String>§last_seen_ptr: Option<String>Implementations§
Source§impl Account
impl Account
pub async fn find_by_pubkey( profile: &str, pubkey: &[u8], database: &Database, ) -> Result<Option<Account>, Error>
Sourcepub async fn find_by_id(
profile: &str,
id: &str,
database: &Database,
) -> Result<Option<Account>, Error>
pub async fn find_by_id( profile: &str, id: &str, database: &Database, ) -> Result<Option<Account>, Error>
Looks an account up by id within one profile. An id is a UUID and
therefore globally unique, so the profile predicate is not about
finding the row: it is what makes an account URL minted at one endpoint
unusable as a kid at another.
Sourcepub async fn find_or_create(
profile: &str,
pubkey: &[u8],
contact: Vec<String>,
client: &ClientContext,
database: &Database,
) -> Result<(Account, bool), Error>
pub async fn find_or_create( profile: &str, pubkey: &[u8], contact: Vec<String>, client: &ClientContext, database: &Database, ) -> Result<(Account, bool), Error>
Looks up the account for pubkey, creating it if absent. Returns the
account and whether it was newly created — RFC 8555 §7.3 find-or-create,
where a repeated key returns the existing account rather than a duplicate.
client is stamped onto the row only on the creating branch: the
created_* columns mean “where this account was registered from”, so a
later newAccount from elsewhere returning the same account must not
rewrite them. Where the key was last used from is last_seen_*, which
Account::touch keeps up to date.
Sourcepub fn needs_touch(&self, now: i64, ip: Option<&str>) -> bool
pub fn needs_touch(&self, now: i64, ip: Option<&str>) -> bool
Whether Account::touch is worth a write at now, for a request
arriving from ip.
Two ways to say yes, and the second is the point of the method existing:
ACCOUNT_TOUCH_INTERVALhas elapsed (or nothing was ever recorded);- the address differs from the last one recorded, whatever the interval says. A key that moves is the single most interesting thing these columns can show, and a throttle that swallowed the move for a minute would hide exactly the requests worth seeing — a stolen account key being used from somewhere new arrives as a burst, not a trickle.
A pure function of the row and its arguments, so the policy is testable without an HTTP request, and it lives beside the columns it governs rather than in the extractor that calls it.
Sourcepub async fn touch(
&mut self,
client: &ClientContext,
database: &Database,
) -> Result<(), Error>
pub async fn touch( &mut self, client: &ClientContext, database: &Database, ) -> Result<(), Error>
Records that this key just authenticated a request, from client.
Called only when Account::needs_touch said so — which is also why
the reverse lookup belongs to the caller: resolving a PTR record for a
write that is about to be skipped would be the cost the throttle exists
to avoid. Keeps the in-memory fields in sync, so a to_json in the same
request reflects it without a re-read.
Sourcepub async fn update_contact(
&mut self,
contact: Vec<String>,
database: &Database,
) -> Result<(), Error>
pub async fn update_contact( &mut self, contact: Vec<String>, database: &Database, ) -> Result<(), Error>
Replaces the account’s contact list (RFC 8555 §7.3.2 account update). The
in-memory self.contact is kept in sync so a subsequent to_json
reflects the change without a re-read.
Sourcepub async fn deactivate(&mut self, database: &Database) -> Result<(), Error>
pub async fn deactivate(&mut self, database: &Database) -> Result<(), Error>
Deactivates the account (RFC 8555 §7.3.6): sets status to deactivated,
a terminal state. Keeps self.status in sync.
Sourcepub async fn update_pubkey(
&mut self,
pubkey: &[u8],
database: &Database,
) -> Result<(), Error>
pub async fn update_pubkey( &mut self, pubkey: &[u8], database: &Database, ) -> Result<(), Error>
Replaces the account’s key (RFC 8555 §7.3.5 account key rollover).
pubkey is DER SPKI, the same form every other lookup keys accounts
by. pubkey is UNIQUE, a backstop against the rare race the
caller’s own pre-check (Account::find_by_pubkey) cannot fully close;
a violation here surfaces as a plain sqlx::Error.
Sourcepub async fn set_eab_kid(
&mut self,
eab_kid: Uuid,
database: &Database,
) -> Result<(), Error>
pub async fn set_eab_kid( &mut self, eab_kid: Uuid, database: &Database, ) -> Result<(), Error>
Records which EAB credential created this account – an audit trail
only (see the migration comment). Called once, right after
Account::find_or_create reports a freshly created row
(post_new_account in lib.rs): never overwritten afterwards, so
re-registering under an existing key does not change what is recorded,
even if a different (still valid) EAB credential is presented that time.
Sourcepub async fn set_terms_agreed(
&mut self,
database: &Database,
) -> Result<(), Error>
pub async fn set_terms_agreed( &mut self, database: &Database, ) -> Result<(), Error>
Records that this account agreed to the terms of service (RFC 8555 §7.3.3).
Same lifecycle as Account::set_eab_kid: called once, right after
find_or_create reports a freshly created row, and never overwritten —
re-registering under an existing key does not restate the agreement,
and a ToS added to the configuration later does not retroactively make
old accounts look like they accepted it.
Sourcepub async fn find_any_by_id(
id: &str,
database: &Database,
) -> Result<Option<Account>, Error>
pub async fn find_any_by_id( id: &str, database: &Database, ) -> Result<Option<Account>, Error>
Looks an account up by id across every profile — for the admin CLI, where an operator holds an id and not necessarily the endpoint it came from. Ids are UUIDs, so this is unambiguous.
Never use it on a request path: profile scoping is what keeps an account URL minted at one endpoint from being accepted at another.
Sourcepub async fn search(
profile: Option<&str>,
limit: i64,
offset: i64,
database: &Database,
) -> Result<(Vec<Account>, i64), Error>
pub async fn search( profile: Option<&str>, limit: i64, offset: i64, database: &Database, ) -> Result<(Vec<Account>, i64), Error>
One page of accounts, newest first, plus the total the same filter matches unpaged.
The Account counterpart to crate::sqlite::order::Order::search,
and the only listing this model offers: an unpaged list_all stood
beside it until account list grew a window, and a second listing whose
ordering disagreed with this one was a page control waiting to skip a
row. profile filters to one endpoint; None lists accounts of every
profile, which is what an operator asking “what is on this server?”
wants.
Two literal statements per branch rather than a builder: with one
optional filter there are only two shapes, and sqlx::query’s
&'static str bound is a guarantee worth keeping where it is free.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Account
impl RefUnwindSafe for Account
impl Send for Account
impl Sync for Account
impl Unpin for Account
impl UnsafeUnpin for Account
impl UnwindSafe for Account
Blanket Implementations§
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more