(** A trainee: the account that owns a routine, a workout in progress, and a logbook. Identity lives in {!Hito_app}, not the core, because the core needs none. A {!credential} is a password verifier, never the password. The hash is computed and checked here so that no other layer sees a plaintext password for longer than one request. *) type id = private string (** An opaque account identity, assigned by an adapter. *) val id : string -> id val id_to_string : id -> string type username = private string (** A normalized username: trimmed and lowercased ASCII. *) type username_error = | Too_short (** Fewer than {!username_min_length} characters. *) | Too_long (** More than {!username_max_length} characters. *) val username_min_length : int val username_max_length : int val username : string -> (username, username_error) result (** Normalizes and validates a username. Trims and lowercases the input, then requires its length within {!username_min_length}..{!username_max_length} inclusive. *) val username_to_string : username -> string val pp_username_error : Format.formatter -> username_error -> unit type credential (** A password verifier. Carries a salted hash, never the password. *) val credential_of_hash : string -> credential (** Wraps a stored hash read back from persistence. *) val credential_to_hash : credential -> string (** The stored hash, for persistence. *) val hash_password : string -> credential (** Salts and hashes a new password. Accepts any string; there is no length or content policy on passwords. *) val verify_password : credential -> string -> bool (** Constant-time verification of a candidate password against the verifier. *) type t = { id : id; username : username; credential : credential } (** A stored account. *)