//! Generated by `trust-tasks-codegen` — do not edit by hand.
//!
//! Spec slug: `auth/step-up/approve-request`. Version: `0.1`.
#[allow(unused_imports)]
use serde::{Deserialize, Serialize};
/// Error types.
pub mod error {
/// Error from a `TryFrom` or `FromStr` implementation.
pub struct ConversionError(::std::borrow::Cow<'static, str>);
impl ::std::error::Error for ConversionError {}
impl ::std::fmt::Display for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
::std::fmt::Display::fmt(&self.0, f)
}
}
impl ::std::fmt::Debug for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
::std::fmt::Debug::fmt(&self.0, f)
}
}
impl From<&'static str> for ConversionError {
fn from(value: &'static str) -> Self {
Self(value.into())
}
}
impl From<String> for ConversionError {
fn from(value: String) -> Self {
Self(value.into())
}
}
}
///`CredentialDescriptor`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "PublicKeyCredentialDescriptor",
/// "type": "object",
/// "required": [
/// "id",
/// "type"
/// ],
/// "properties": {
/// "id": {
/// "description": "base64url-encoded credential id.",
/// "type": "string"
/// },
/// "transports": {
/// "type": "array",
/// "items": {
/// "enum": [
/// "usb",
/// "nfc",
/// "ble",
/// "internal",
/// "hybrid"
/// ]
/// }
/// },
/// "type": {
/// "const": "public-key"
/// }
/// },
/// "additionalProperties": false,
/// "$anchor": "credentialDescriptor"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct CredentialDescriptor {
///base64url-encoded credential id.
pub id: ::std::string::String,
#[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")]
pub transports: ::std::vec::Vec<CredentialDescriptorTransportsItem>,
#[serde(rename = "type")]
pub type_: ::serde_json::Value,
}
impl ::std::convert::From<&CredentialDescriptor> for CredentialDescriptor {
fn from(value: &CredentialDescriptor) -> Self {
value.clone()
}
}
///`CredentialDescriptorTransportsItem`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "enum": [
/// "usb",
/// "nfc",
/// "ble",
/// "internal",
/// "hybrid"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
pub enum CredentialDescriptorTransportsItem {
#[serde(rename = "usb")]
Usb,
#[serde(rename = "nfc")]
Nfc,
#[serde(rename = "ble")]
Ble,
#[serde(rename = "internal")]
Internal,
#[serde(rename = "hybrid")]
Hybrid,
}
impl ::std::convert::From<&Self> for CredentialDescriptorTransportsItem {
fn from(value: &CredentialDescriptorTransportsItem) -> Self {
value.clone()
}
}
impl ::std::fmt::Display for CredentialDescriptorTransportsItem {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Usb => f.write_str("usb"),
Self::Nfc => f.write_str("nfc"),
Self::Ble => f.write_str("ble"),
Self::Internal => f.write_str("internal"),
Self::Hybrid => f.write_str("hybrid"),
}
}
}
impl ::std::str::FromStr for CredentialDescriptorTransportsItem {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"usb" => Ok(Self::Usb),
"nfc" => Ok(Self::Nfc),
"ble" => Ok(Self::Ble),
"internal" => Ok(Self::Internal),
"hybrid" => Ok(Self::Hybrid),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for CredentialDescriptorTransportsItem {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for CredentialDescriptorTransportsItem {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for CredentialDescriptorTransportsItem {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///Server-issued options for `navigator.credentials.get({ publicKey: ... })`.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "PublicKeyCredentialRequestOptions",
/// "description": "Server-issued options for `navigator.credentials.get({ publicKey: ... })`.",
/// "type": "object",
/// "required": [
/// "challenge"
/// ],
/// "properties": {
/// "allowCredentials": {
/// "type": "array",
/// "items": {
/// "$ref": "#/definitions/CredentialDescriptor"
/// }
/// },
/// "challenge": {
/// "description": "base64url-encoded one-time nonce.",
/// "type": "string"
/// },
/// "rpId": {
/// "type": "string",
/// "minLength": 1
/// },
/// "timeout": {
/// "type": "integer",
/// "minimum": 1.0
/// },
/// "userVerification": {
/// "enum": [
/// "discouraged",
/// "preferred",
/// "required"
/// ]
/// }
/// },
/// "additionalProperties": false,
/// "$anchor": "credentialRequestOptions"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct CredentialRequestOptions {
#[serde(
rename = "allowCredentials",
default,
skip_serializing_if = "::std::vec::Vec::is_empty"
)]
pub allow_credentials: ::std::vec::Vec<CredentialDescriptor>,
///base64url-encoded one-time nonce.
pub challenge: ::std::string::String,
#[serde(
rename = "rpId",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub rp_id: ::std::option::Option<CredentialRequestOptionsRpId>,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub timeout: ::std::option::Option<::std::num::NonZeroU64>,
#[serde(
rename = "userVerification",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub user_verification: ::std::option::Option<CredentialRequestOptionsUserVerification>,
}
impl ::std::convert::From<&CredentialRequestOptions> for CredentialRequestOptions {
fn from(value: &CredentialRequestOptions) -> Self {
value.clone()
}
}
///`CredentialRequestOptionsRpId`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct CredentialRequestOptionsRpId(::std::string::String);
impl ::std::ops::Deref for CredentialRequestOptionsRpId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<CredentialRequestOptionsRpId> for ::std::string::String {
fn from(value: CredentialRequestOptionsRpId) -> Self {
value.0
}
}
impl ::std::convert::From<&CredentialRequestOptionsRpId> for CredentialRequestOptionsRpId {
fn from(value: &CredentialRequestOptionsRpId) -> Self {
value.clone()
}
}
impl ::std::str::FromStr for CredentialRequestOptionsRpId {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for CredentialRequestOptionsRpId {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for CredentialRequestOptionsRpId {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for CredentialRequestOptionsRpId {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for CredentialRequestOptionsRpId {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///`CredentialRequestOptionsUserVerification`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "enum": [
/// "discouraged",
/// "preferred",
/// "required"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
pub enum CredentialRequestOptionsUserVerification {
#[serde(rename = "discouraged")]
Discouraged,
#[serde(rename = "preferred")]
Preferred,
#[serde(rename = "required")]
Required,
}
impl ::std::convert::From<&Self> for CredentialRequestOptionsUserVerification {
fn from(value: &CredentialRequestOptionsUserVerification) -> Self {
value.clone()
}
}
impl ::std::fmt::Display for CredentialRequestOptionsUserVerification {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Discouraged => f.write_str("discouraged"),
Self::Preferred => f.write_str("preferred"),
Self::Required => f.write_str("required"),
}
}
}
impl ::std::str::FromStr for CredentialRequestOptionsUserVerification {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"discouraged" => Ok(Self::Discouraged),
"preferred" => Ok(Self::Preferred),
"required" => Ok(Self::Required),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for CredentialRequestOptionsUserVerification {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for CredentialRequestOptionsUserVerification {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for CredentialRequestOptionsUserVerification {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Ext",
/// "description": "Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.",
/// "type": "object",
/// "minProperties": 1,
/// "additionalProperties": true,
/// "propertyNames": {
/// "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$"
/// }
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(transparent)]
pub struct Ext(pub ::std::collections::HashMap<ExtKey, ::serde_json::Value>);
impl ::std::ops::Deref for Ext {
type Target = ::std::collections::HashMap<ExtKey, ::serde_json::Value>;
fn deref(&self) -> &::std::collections::HashMap<ExtKey, ::serde_json::Value> {
&self.0
}
}
impl ::std::convert::From<Ext> for ::std::collections::HashMap<ExtKey, ::serde_json::Value> {
fn from(value: Ext) -> Self {
value.0
}
}
impl ::std::convert::From<&Ext> for Ext {
fn from(value: &Ext) -> Self {
value.clone()
}
}
impl ::std::convert::From<::std::collections::HashMap<ExtKey, ::serde_json::Value>> for Ext {
fn from(value: ::std::collections::HashMap<ExtKey, ::serde_json::Value>) -> Self {
Self(value)
}
}
///`ExtKey`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "string",
/// "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct ExtKey(::std::string::String);
impl ::std::ops::Deref for ExtKey {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<ExtKey> for ::std::string::String {
fn from(value: ExtKey) -> Self {
value.0
}
}
impl ::std::convert::From<&ExtKey> for ExtKey {
fn from(value: &ExtKey) -> Self {
value.clone()
}
}
impl ::std::str::FromStr for ExtKey {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| {
::regress::Regex::new("^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$").unwrap()
});
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for ExtKey {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///A relying party asks an approver (typically a wallet or a VTA) to ratify an AAL elevation for a subject's session.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "$id": "https://trusttasks.org/spec/auth/step-up/approve-request/0.1",
/// "title": "Payload",
/// "description": "A relying party asks an approver (typically a wallet or a VTA) to ratify an AAL elevation for a subject's session.",
/// "type": "object",
/// "required": [
/// "challenge",
/// "reason",
/// "sessionId",
/// "subject"
/// ],
/// "properties": {
/// "acceptableEvidence": {
/// "description": "Which approve-response evidence kinds the relying party will accept (see auth/step-up/approve-response `evidence`). When omitted, the approver MAY use any kind it supports. An approver that cannot satisfy any listed kind SHOULD refuse with `method_unsupported`.",
/// "type": "array",
/// "items": {
/// "type": "string",
/// "enum": [
/// "did-signed",
/// "webauthn"
/// ]
/// },
/// "minItems": 1,
/// "uniqueItems": true
/// },
/// "challenge": {
/// "description": "base64url-encoded nonce the approver will include in the approve-response signature. ≥128 bits entropy.",
/// "type": "string",
/// "minLength": 16
/// },
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// },
/// "reason": {
/// "description": "Human-readable explanation of WHY the relying party is asking (e.g. \"confirm transfer of 1000 USD to bob.example\"). Surfaced to the user by the approver for consent. SHOULD be specific enough that a user can refuse intelligently.",
/// "type": "string",
/// "minLength": 1
/// },
/// "sessionId": {
/// "description": "The session the relying party wants elevated. Opaque to the approver.",
/// "type": "string",
/// "minLength": 1
/// },
/// "subject": {
/// "description": "The VID whose session is being elevated. The approver MUST verify this is a VID it can speak for.",
/// "type": "string",
/// "minLength": 1
/// },
/// "targetAcr": {
/// "description": "The acr the relying party expects on the elevated session. Approvers MAY refuse if they cannot deliver this level.",
/// "type": "string"
/// },
/// "ttl": {
/// "description": "Seconds within which the relying party expects the approve-response. Approvers SHOULD treat as advisory — the relying party's own expiry policy is authoritative.",
/// "type": "integer",
/// "minimum": 1.0
/// },
/// "webauthn": {
/// "description": "Optional WebAuthn `PublicKeyCredentialRequestOptions` the approver passes to the platform passkey API when producing `webauthn` evidence. When present, its `challenge` MUST equal `payload.challenge` so the resulting assertion binds the same nonce the relying party bound server-side. `rpId`/`allowCredentials` identify which credential the approver should assert with.",
/// "$ref": "#/definitions/CredentialRequestOptions"
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct Payload {
///Which approve-response evidence kinds the relying party will accept (see auth/step-up/approve-response `evidence`). When omitted, the approver MAY use any kind it supports. An approver that cannot satisfy any listed kind SHOULD refuse with `method_unsupported`.
#[serde(
rename = "acceptableEvidence",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub acceptable_evidence: ::std::option::Option<Vec<PayloadAcceptableEvidenceItem>>,
///base64url-encoded nonce the approver will include in the approve-response signature. ≥128 bits entropy.
pub challenge: PayloadChallenge,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///Human-readable explanation of WHY the relying party is asking (e.g. "confirm transfer of 1000 USD to bob.example"). Surfaced to the user by the approver for consent. SHOULD be specific enough that a user can refuse intelligently.
pub reason: PayloadReason,
///The session the relying party wants elevated. Opaque to the approver.
#[serde(rename = "sessionId")]
pub session_id: PayloadSessionId,
///The VID whose session is being elevated. The approver MUST verify this is a VID it can speak for.
pub subject: PayloadSubject,
///The acr the relying party expects on the elevated session. Approvers MAY refuse if they cannot deliver this level.
#[serde(
rename = "targetAcr",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub target_acr: ::std::option::Option<::std::string::String>,
///Seconds within which the relying party expects the approve-response. Approvers SHOULD treat as advisory — the relying party's own expiry policy is authoritative.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ttl: ::std::option::Option<::std::num::NonZeroU64>,
///Optional WebAuthn `PublicKeyCredentialRequestOptions` the approver passes to the platform passkey API when producing `webauthn` evidence. When present, its `challenge` MUST equal `payload.challenge` so the resulting assertion binds the same nonce the relying party bound server-side. `rpId`/`allowCredentials` identify which credential the approver should assert with.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub webauthn: ::std::option::Option<CredentialRequestOptions>,
}
impl ::std::convert::From<&Payload> for Payload {
fn from(value: &Payload) -> Self {
value.clone()
}
}
///`PayloadAcceptableEvidenceItem`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "string",
/// "enum": [
/// "did-signed",
/// "webauthn"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
pub enum PayloadAcceptableEvidenceItem {
#[serde(rename = "did-signed")]
DidSigned,
#[serde(rename = "webauthn")]
Webauthn,
}
impl ::std::convert::From<&Self> for PayloadAcceptableEvidenceItem {
fn from(value: &PayloadAcceptableEvidenceItem) -> Self {
value.clone()
}
}
impl ::std::fmt::Display for PayloadAcceptableEvidenceItem {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::DidSigned => f.write_str("did-signed"),
Self::Webauthn => f.write_str("webauthn"),
}
}
}
impl ::std::str::FromStr for PayloadAcceptableEvidenceItem {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"did-signed" => Ok(Self::DidSigned),
"webauthn" => Ok(Self::Webauthn),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for PayloadAcceptableEvidenceItem {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for PayloadAcceptableEvidenceItem {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for PayloadAcceptableEvidenceItem {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///base64url-encoded nonce the approver will include in the approve-response signature. ≥128 bits entropy.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "base64url-encoded nonce the approver will include in the approve-response signature. ≥128 bits entropy.",
/// "type": "string",
/// "minLength": 16
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadChallenge(::std::string::String);
impl ::std::ops::Deref for PayloadChallenge {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadChallenge> for ::std::string::String {
fn from(value: PayloadChallenge) -> Self {
value.0
}
}
impl ::std::convert::From<&PayloadChallenge> for PayloadChallenge {
fn from(value: &PayloadChallenge) -> Self {
value.clone()
}
}
impl ::std::str::FromStr for PayloadChallenge {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 16usize {
return Err("shorter than 16 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadChallenge {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for PayloadChallenge {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for PayloadChallenge {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for PayloadChallenge {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///Human-readable explanation of WHY the relying party is asking (e.g. "confirm transfer of 1000 USD to bob.example"). Surfaced to the user by the approver for consent. SHOULD be specific enough that a user can refuse intelligently.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Human-readable explanation of WHY the relying party is asking (e.g. \"confirm transfer of 1000 USD to bob.example\"). Surfaced to the user by the approver for consent. SHOULD be specific enough that a user can refuse intelligently.",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadReason(::std::string::String);
impl ::std::ops::Deref for PayloadReason {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadReason> for ::std::string::String {
fn from(value: PayloadReason) -> Self {
value.0
}
}
impl ::std::convert::From<&PayloadReason> for PayloadReason {
fn from(value: &PayloadReason) -> Self {
value.clone()
}
}
impl ::std::str::FromStr for PayloadReason {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadReason {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for PayloadReason {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for PayloadReason {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for PayloadReason {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///The session the relying party wants elevated. Opaque to the approver.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The session the relying party wants elevated. Opaque to the approver.",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadSessionId(::std::string::String);
impl ::std::ops::Deref for PayloadSessionId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadSessionId> for ::std::string::String {
fn from(value: PayloadSessionId) -> Self {
value.0
}
}
impl ::std::convert::From<&PayloadSessionId> for PayloadSessionId {
fn from(value: &PayloadSessionId) -> Self {
value.clone()
}
}
impl ::std::str::FromStr for PayloadSessionId {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadSessionId {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for PayloadSessionId {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for PayloadSessionId {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for PayloadSessionId {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///The VID whose session is being elevated. The approver MUST verify this is a VID it can speak for.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The VID whose session is being elevated. The approver MUST verify this is a VID it can speak for.",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadSubject(::std::string::String);
impl ::std::ops::Deref for PayloadSubject {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadSubject> for ::std::string::String {
fn from(value: PayloadSubject) -> Self {
value.0
}
}
impl ::std::convert::From<&PayloadSubject> for PayloadSubject {
fn from(value: &PayloadSubject) -> Self {
value.clone()
}
}
impl ::std::str::FromStr for PayloadSubject {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadSubject {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for PayloadSubject {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for PayloadSubject {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for PayloadSubject {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///Synchronous ack from the approver acknowledging it received the request and will (or will not) deliver an approve-response. Carried in a Trust Task document whose type is https://trusttasks.org/spec/auth/step-up/approve-request/0.1#response.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Response",
/// "description": "Synchronous ack from the approver acknowledging it received the request and will (or will not) deliver an approve-response. Carried in a Trust Task document whose type is https://trusttasks.org/spec/auth/step-up/approve-request/0.1#response.",
/// "type": "object",
/// "required": [
/// "status"
/// ],
/// "properties": {
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// },
/// "reason": {
/// "description": "Required when status is `refused`.",
/// "type": "string"
/// },
/// "status": {
/// "description": "`accepted` means the approver received the request and will return an approve-response (typically via DIDComm). `refused` means it will not — `reason` MUST be set.",
/// "type": "string",
/// "enum": [
/// "accepted",
/// "refused"
/// ]
/// }
/// },
/// "additionalProperties": false,
/// "$anchor": "response"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct Response {
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///Required when status is `refused`.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub reason: ::std::option::Option<::std::string::String>,
///`accepted` means the approver received the request and will return an approve-response (typically via DIDComm). `refused` means it will not — `reason` MUST be set.
pub status: ResponseStatus,
}
impl ::std::convert::From<&Response> for Response {
fn from(value: &Response) -> Self {
value.clone()
}
}
///`accepted` means the approver received the request and will return an approve-response (typically via DIDComm). `refused` means it will not — `reason` MUST be set.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "`accepted` means the approver received the request and will return an approve-response (typically via DIDComm). `refused` means it will not — `reason` MUST be set.",
/// "type": "string",
/// "enum": [
/// "accepted",
/// "refused"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
pub enum ResponseStatus {
#[serde(rename = "accepted")]
Accepted,
#[serde(rename = "refused")]
Refused,
}
impl ::std::convert::From<&Self> for ResponseStatus {
fn from(value: &ResponseStatus) -> Self {
value.clone()
}
}
impl ::std::fmt::Display for ResponseStatus {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Accepted => f.write_str("accepted"),
Self::Refused => f.write_str("refused"),
}
}
}
impl ::std::str::FromStr for ResponseStatus {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"accepted" => Ok(Self::Accepted),
"refused" => Ok(Self::Refused),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for ResponseStatus {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for ResponseStatus {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for ResponseStatus {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl crate::Payload for Payload {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/auth/step-up/approve-request/0.1";
const IS_PROOF_REQUIRED: bool = true;
}
impl crate::Payload for Response {
const TYPE_URI: &'static str =
"https://trusttasks.org/spec/auth/step-up/approve-request/0.1#response";
const IS_PROOF_REQUIRED: bool = true;
}
#[cfg(feature = "validate")]
impl crate::validate::ValidatedPayload for Payload {
const SCHEMA_JSON: &'static str = "{\n \"$defs\": {\n \"CredentialDescriptor\": {\n \"$anchor\": \"credentialDescriptor\",\n \"additionalProperties\": false,\n \"properties\": {\n \"id\": {\n \"description\": \"base64url-encoded credential id.\",\n \"type\": \"string\"\n },\n \"transports\": {\n \"items\": {\n \"enum\": [\n \"usb\",\n \"nfc\",\n \"ble\",\n \"internal\",\n \"hybrid\"\n ]\n },\n \"type\": \"array\"\n },\n \"type\": {\n \"const\": \"public-key\"\n }\n },\n \"required\": [\n \"type\",\n \"id\"\n ],\n \"title\": \"PublicKeyCredentialDescriptor\",\n \"type\": \"object\"\n },\n \"CredentialRequestOptions\": {\n \"$anchor\": \"credentialRequestOptions\",\n \"additionalProperties\": false,\n \"description\": \"Server-issued options for `navigator.credentials.get({ publicKey: ... })`.\",\n \"properties\": {\n \"allowCredentials\": {\n \"items\": {\n \"$ref\": \"#/$defs/CredentialDescriptor\"\n },\n \"type\": \"array\"\n },\n \"challenge\": {\n \"description\": \"base64url-encoded one-time nonce.\",\n \"type\": \"string\"\n },\n \"rpId\": {\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"timeout\": {\n \"minimum\": 1,\n \"type\": \"integer\"\n },\n \"userVerification\": {\n \"enum\": [\n \"discouraged\",\n \"preferred\",\n \"required\"\n ]\n }\n },\n \"required\": [\n \"challenge\"\n ],\n \"title\": \"PublicKeyCredentialRequestOptions\",\n \"type\": \"object\"\n },\n \"Ext\": {\n \"additionalProperties\": true,\n \"description\": \"Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.\",\n \"minProperties\": 1,\n \"propertyNames\": {\n \"pattern\": \"^[a-z][a-z0-9-]*(\\\\.[a-z0-9-]+)+$\"\n },\n \"title\": \"Ext\",\n \"type\": \"object\"\n },\n \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"description\": \"Synchronous ack from the approver acknowledging it received the request and will (or will not) deliver an approve-response. Carried in a Trust Task document whose type is https://trusttasks.org/spec/auth/step-up/approve-request/0.1#response.\",\n \"properties\": {\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"reason\": {\n \"description\": \"Required when status is `refused`.\",\n \"type\": \"string\"\n },\n \"status\": {\n \"description\": \"`accepted` means the approver received the request and will return an approve-response (typically via DIDComm). `refused` means it will not — `reason` MUST be set.\",\n \"enum\": [\n \"accepted\",\n \"refused\"\n ],\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"title\": \"Auth Step-up Approve Request — response payload\",\n \"type\": \"object\"\n }\n },\n \"$id\": \"https://trusttasks.org/spec/auth/step-up/approve-request/0.1\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"description\": \"A relying party asks an approver (typically a wallet or a VTA) to ratify an AAL elevation for a subject's session.\",\n \"properties\": {\n \"acceptableEvidence\": {\n \"description\": \"Which approve-response evidence kinds the relying party will accept (see auth/step-up/approve-response `evidence`). When omitted, the approver MAY use any kind it supports. An approver that cannot satisfy any listed kind SHOULD refuse with `method_unsupported`.\",\n \"items\": {\n \"enum\": [\n \"did-signed\",\n \"webauthn\"\n ],\n \"type\": \"string\"\n },\n \"minItems\": 1,\n \"type\": \"array\",\n \"uniqueItems\": true\n },\n \"challenge\": {\n \"description\": \"base64url-encoded nonce the approver will include in the approve-response signature. ≥128 bits entropy.\",\n \"minLength\": 16,\n \"type\": \"string\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"reason\": {\n \"description\": \"Human-readable explanation of WHY the relying party is asking (e.g. \\\"confirm transfer of 1000 USD to bob.example\\\"). Surfaced to the user by the approver for consent. SHOULD be specific enough that a user can refuse intelligently.\",\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"description\": \"The session the relying party wants elevated. Opaque to the approver.\",\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"subject\": {\n \"description\": \"The VID whose session is being elevated. The approver MUST verify this is a VID it can speak for.\",\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"targetAcr\": {\n \"description\": \"The acr the relying party expects on the elevated session. Approvers MAY refuse if they cannot deliver this level.\",\n \"type\": \"string\"\n },\n \"ttl\": {\n \"description\": \"Seconds within which the relying party expects the approve-response. Approvers SHOULD treat as advisory — the relying party's own expiry policy is authoritative.\",\n \"minimum\": 1,\n \"type\": \"integer\"\n },\n \"webauthn\": {\n \"$ref\": \"#/$defs/CredentialRequestOptions\",\n \"description\": \"Optional WebAuthn `PublicKeyCredentialRequestOptions` the approver passes to the platform passkey API when producing `webauthn` evidence. When present, its `challenge` MUST equal `payload.challenge` so the resulting assertion binds the same nonce the relying party bound server-side. `rpId`/`allowCredentials` identify which credential the approver should assert with.\"\n }\n },\n \"required\": [\n \"subject\",\n \"sessionId\",\n \"challenge\",\n \"reason\"\n ],\n \"title\": \"Auth — Step-up Approve Request\",\n \"type\": \"object\"\n}\n";
}
#[cfg(test)]
mod conformance {
//! Round-trip tests harvested from the spec's `spec.md`,
//! plus a `rejects_invalid_examples` test for any fixtures
//! in `payload.invalid-examples.json` (validate feature).
#[test]
fn response_example_1() {
const JSON: &str = "{\n \"id\": \"step-up-resp-3456-7890-1234-567890abcdef\",\n \"type\": \"https://trusttasks.org/spec/auth/step-up/approve-request/0.1#response\",\n \"threadId\": \"step-up-1234-5678-90ab-cdef12345678\",\n \"issuer\": \"did:web:alice.example\",\n \"recipient\": \"did:web:bank.example\",\n \"issuedAt\": \"2026-05-23T14:00:01Z\",\n \"payload\": {\n \"status\": \"accepted\"\n }\n}\n";
let doc: crate::TrustTask<super::Response> =
serde_json::from_str(JSON).expect("deserialize response example");
let rendered = serde_json::to_value(&doc).expect("re-serialize");
let expected: serde_json::Value = serde_json::from_str(JSON).expect("re-parse expected");
assert_eq!(rendered, expected, "response example failed round-trip");
}
#[test]
fn response_example_2() {
const JSON: &str = "{\n \"id\": \"step-up-resp-4567-8901-2345-67890abcdef0\",\n \"type\": \"https://trusttasks.org/spec/auth/step-up/approve-request/0.1#response\",\n \"threadId\": \"step-up-1234-5678-90ab-cdef12345678\",\n \"issuer\": \"did:web:alice.example\",\n \"recipient\": \"did:web:bank.example\",\n \"issuedAt\": \"2026-05-23T14:00:01Z\",\n \"payload\": {\n \"status\": \"refused\",\n \"reason\": \"User declined\"\n }\n}\n";
let doc: crate::TrustTask<super::Response> =
serde_json::from_str(JSON).expect("deserialize response example");
let rendered = serde_json::to_value(&doc).expect("re-serialize");
let expected: serde_json::Value = serde_json::from_str(JSON).expect("re-parse expected");
assert_eq!(rendered, expected, "response example failed round-trip");
}
/// Each fixture in `payload.invalid-examples.json` MUST be
/// rejected by at least one of: serde deserialization, or
/// JSON-Schema validation under the `validate` feature. The
/// fixture file documents the producer-side bug class that
/// each payload exemplifies; this generated test pins it.
#[cfg(feature = "validate")]
#[test]
fn rejects_invalid_examples() {
use crate::validate::ValidatedPayload;
let fixtures: &[(&str, &str)] = &[
(
"Missing required subject.",
"{\n \"challenge\": \"Q2hhbExlbmdlVmFsdWVCYXNlNjQ\",\n \"reason\": \"Confirm transfer\",\n \"sessionId\": \"ec5d3c89\"\n}",
),
(
"Missing required reason — approver MUST surface a user-readable rationale.",
"{\n \"challenge\": \"Q2hhbExlbmdlVmFsdWVCYXNlNjQ\",\n \"sessionId\": \"ec5d3c89\",\n \"subject\": \"did:web:alice.example\"\n}",
),
(
"Challenge too short.",
"{\n \"challenge\": \"short\",\n \"reason\": \"Confirm transfer\",\n \"sessionId\": \"ec5d3c89\",\n \"subject\": \"did:web:alice.example\"\n}",
),
];
for (i, (note, raw)) in fixtures.iter().enumerate() {
let value: serde_json::Value = match serde_json::from_str(raw) {
Ok(v) => v,
Err(_) => continue,
};
let serde_ok = serde_json::from_value::<super::Payload>(value.clone()).is_ok();
let schema_ok = super::Payload::validate_value(&value).is_ok();
assert!(
!(serde_ok && schema_ok),
"invalid-example #{} ({:?}) was accepted by both serde and JSON Schema; \
the fixture's stated failure class is no longer caught:\n{}",
i + 1,
note,
raw
);
}
}
}