//! Generated by `trust-tasks-codegen` — do not edit by hand.
//!
//! Spec slug: `auth/step-up/approve-response`. 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())
}
}
}
///The credential the client returns from `navigator.credentials.get`. Binary fields are base64url-encoded.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "AuthenticatorAssertionResponse (login)",
/// "description": "The credential the client returns from `navigator.credentials.get`. Binary fields are base64url-encoded.",
/// "type": "object",
/// "required": [
/// "id",
/// "rawId",
/// "response",
/// "type"
/// ],
/// "properties": {
/// "authenticatorAttachment": {
/// "enum": [
/// "platform",
/// "cross-platform"
/// ]
/// },
/// "clientExtensionResults": {
/// "type": "object"
/// },
/// "id": {
/// "type": "string"
/// },
/// "rawId": {
/// "type": "string"
/// },
/// "response": {
/// "type": "object",
/// "required": [
/// "authenticatorData",
/// "clientDataJSON",
/// "signature"
/// ],
/// "properties": {
/// "authenticatorData": {
/// "type": "string"
/// },
/// "clientDataJSON": {
/// "type": "string"
/// },
/// "signature": {
/// "type": "string"
/// },
/// "userHandle": {
/// "type": [
/// "string",
/// "null"
/// ]
/// }
/// },
/// "additionalProperties": false
/// },
/// "type": {
/// "const": "public-key"
/// }
/// },
/// "additionalProperties": false,
/// "$anchor": "assertionResponse"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct AssertionResponse {
#[serde(
rename = "authenticatorAttachment",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub authenticator_attachment: ::std::option::Option<AssertionResponseAuthenticatorAttachment>,
#[serde(
rename = "clientExtensionResults",
default,
skip_serializing_if = "::serde_json::Map::is_empty"
)]
pub client_extension_results: ::serde_json::Map<::std::string::String, ::serde_json::Value>,
pub id: ::std::string::String,
#[serde(rename = "rawId")]
pub raw_id: ::std::string::String,
pub response: AssertionResponseResponse,
#[serde(rename = "type")]
pub type_: ::serde_json::Value,
}
impl ::std::convert::From<&AssertionResponse> for AssertionResponse {
fn from(value: &AssertionResponse) -> Self {
value.clone()
}
}
///`AssertionResponseAuthenticatorAttachment`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "enum": [
/// "platform",
/// "cross-platform"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
pub enum AssertionResponseAuthenticatorAttachment {
#[serde(rename = "platform")]
Platform,
#[serde(rename = "cross-platform")]
CrossPlatform,
}
impl ::std::convert::From<&Self> for AssertionResponseAuthenticatorAttachment {
fn from(value: &AssertionResponseAuthenticatorAttachment) -> Self {
value.clone()
}
}
impl ::std::fmt::Display for AssertionResponseAuthenticatorAttachment {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Platform => f.write_str("platform"),
Self::CrossPlatform => f.write_str("cross-platform"),
}
}
}
impl ::std::str::FromStr for AssertionResponseAuthenticatorAttachment {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"platform" => Ok(Self::Platform),
"cross-platform" => Ok(Self::CrossPlatform),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for AssertionResponseAuthenticatorAttachment {
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 AssertionResponseAuthenticatorAttachment {
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 AssertionResponseAuthenticatorAttachment {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///`AssertionResponseResponse`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "object",
/// "required": [
/// "authenticatorData",
/// "clientDataJSON",
/// "signature"
/// ],
/// "properties": {
/// "authenticatorData": {
/// "type": "string"
/// },
/// "clientDataJSON": {
/// "type": "string"
/// },
/// "signature": {
/// "type": "string"
/// },
/// "userHandle": {
/// "type": [
/// "string",
/// "null"
/// ]
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct AssertionResponseResponse {
#[serde(rename = "authenticatorData")]
pub authenticator_data: ::std::string::String,
#[serde(rename = "clientDataJSON")]
pub client_data_json: ::std::string::String,
pub signature: ::std::string::String,
#[serde(
rename = "userHandle",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub user_handle: ::std::option::Option<::std::string::String>,
}
impl ::std::convert::From<&AssertionResponseResponse> for AssertionResponseResponse {
fn from(value: &AssertionResponseResponse) -> Self {
value.clone()
}
}
///Tagged union over `kind` describing the cryptographic factor the approver presents to back the elevation. Pluggable so new factor kinds can be added without disturbing existing consumers (SPEC §4.5.1 ext rules apply to unknown kinds at the producer's discretion).
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Step-up Evidence",
/// "description": "Tagged union over `kind` describing the cryptographic factor the approver presents to back the elevation. Pluggable so new factor kinds can be added without disturbing existing consumers (SPEC §4.5.1 ext rules apply to unknown kinds at the producer's discretion).",
/// "oneOf": [
/// {
/// "title": "DidSigned",
/// "description": "The elevation is gated by the document's framework `proof` — a Data Integrity signature from a key the subject controls (SPEC §4.7). This is the default when `evidence` is omitted. `amr` reflects \"vta\"/\"did\".",
/// "type": "object",
/// "required": [
/// "kind"
/// ],
/// "properties": {
/// "kind": {
/// "const": "did-signed"
/// }
/// },
/// "additionalProperties": false
/// },
/// {
/// "title": "WebAuthn",
/// "type": "object",
/// "required": [
/// "assertion",
/// "kind"
/// ],
/// "properties": {
/// "assertion": {
/// "description": "The unmodified AuthenticatorAssertionResponse from the platform WebAuthn API (`navigator.credentials.get` / ASAuthorization / Credential Manager). Its `clientDataJSON` challenge MUST equal the step-up `challenge`. The relying party verifies it per WebAuthn Level 2 §7.2 exactly as auth/passkey/login/finish does; the assertion is the gate and `amr` reflects \"passkey\".",
/// "$ref": "#/definitions/AssertionResponse"
/// },
/// "kind": {
/// "const": "webauthn"
/// }
/// },
/// "additionalProperties": false
/// }
/// ],
/// "$anchor": "evidence"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(tag = "kind", content = "assertion")]
pub enum Evidence {
#[serde(rename = "did-signed")]
DidSigned,
///WebAuthn
#[serde(rename = "webauthn")]
Webauthn(AssertionResponse),
}
impl ::std::convert::From<&Self> for Evidence {
fn from(value: &Evidence) -> Self {
value.clone()
}
}
impl ::std::convert::From<AssertionResponse> for Evidence {
fn from(value: AssertionResponse) -> Self {
Self::Webauthn(value)
}
}
///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())
})
}
}
///The approver's signed ratification of a step-up: subject + sessionId + challenge are echoed inside a proof-bearing document so the relying party can elevate the session.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "$id": "https://trusttasks.org/spec/auth/step-up/approve-response/0.1",
/// "title": "Payload",
/// "description": "The approver's signed ratification of a step-up: subject + sessionId + challenge are echoed inside a proof-bearing document so the relying party can elevate the session.",
/// "type": "object",
/// "required": [
/// "challenge",
/// "decision",
/// "sessionId",
/// "subject"
/// ],
/// "properties": {
/// "challenge": {
/// "description": "Echoed from the matching approve-request. The relying party verifies it equals the bound challenge.",
/// "type": "string",
/// "minLength": 16
/// },
/// "decision": {
/// "description": "`approved` elevates the session per the relying party's policy. `denied` is a signed refusal — useful for audit even though it elevates nothing.",
/// "type": "string",
/// "enum": [
/// "approved",
/// "denied"
/// ]
/// },
/// "deniedReason": {
/// "description": "Required when decision is `denied`. Human-readable rationale the user provided (or which the approver inferred).",
/// "type": "string"
/// },
/// "evidence": {
/// "description": "How the approver demonstrated the factor backing this elevation. A tagged union on `kind`. When `evidence` is absent the elevation is gated solely by the document's framework `proof` (equivalent to `kind: did-signed`). When `kind: webauthn` is supplied, the carried WebAuthn assertion over `challenge` is the gate and the framework `proof` MAY be omitted.",
/// "$ref": "#/definitions/Evidence"
/// },
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// },
/// "grantedAcr": {
/// "description": "The acr the approver believes it has cryptographically demonstrated. The relying party MAY accept this, MAY upgrade to a lower value, but MUST NOT exceed it.",
/// "type": "string"
/// },
/// "sessionId": {
/// "description": "Echoed from the matching approve-request. The relying party uses it to locate the session to elevate.",
/// "type": "string",
/// "minLength": 1
/// },
/// "subject": {
/// "description": "Echoed from the matching approve-request. The relying party verifies it equals the session's subject.",
/// "type": "string",
/// "minLength": 1
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct Payload {
///Echoed from the matching approve-request. The relying party verifies it equals the bound challenge.
pub challenge: PayloadChallenge,
///`approved` elevates the session per the relying party's policy. `denied` is a signed refusal — useful for audit even though it elevates nothing.
pub decision: PayloadDecision,
///Required when decision is `denied`. Human-readable rationale the user provided (or which the approver inferred).
#[serde(
rename = "deniedReason",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub denied_reason: ::std::option::Option<::std::string::String>,
///How the approver demonstrated the factor backing this elevation. A tagged union on `kind`. When `evidence` is absent the elevation is gated solely by the document's framework `proof` (equivalent to `kind: did-signed`). When `kind: webauthn` is supplied, the carried WebAuthn assertion over `challenge` is the gate and the framework `proof` MAY be omitted.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub evidence: ::std::option::Option<Evidence>,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///The acr the approver believes it has cryptographically demonstrated. The relying party MAY accept this, MAY upgrade to a lower value, but MUST NOT exceed it.
#[serde(
rename = "grantedAcr",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub granted_acr: ::std::option::Option<::std::string::String>,
///Echoed from the matching approve-request. The relying party uses it to locate the session to elevate.
#[serde(rename = "sessionId")]
pub session_id: PayloadSessionId,
///Echoed from the matching approve-request. The relying party verifies it equals the session's subject.
pub subject: PayloadSubject,
}
impl ::std::convert::From<&Payload> for Payload {
fn from(value: &Payload) -> Self {
value.clone()
}
}
///Echoed from the matching approve-request. The relying party verifies it equals the bound challenge.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Echoed from the matching approve-request. The relying party verifies it equals the bound challenge.",
/// "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())
})
}
}
///`approved` elevates the session per the relying party's policy. `denied` is a signed refusal — useful for audit even though it elevates nothing.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "`approved` elevates the session per the relying party's policy. `denied` is a signed refusal — useful for audit even though it elevates nothing.",
/// "type": "string",
/// "enum": [
/// "approved",
/// "denied"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
pub enum PayloadDecision {
#[serde(rename = "approved")]
Approved,
#[serde(rename = "denied")]
Denied,
}
impl ::std::convert::From<&Self> for PayloadDecision {
fn from(value: &PayloadDecision) -> Self {
value.clone()
}
}
impl ::std::fmt::Display for PayloadDecision {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Approved => f.write_str("approved"),
Self::Denied => f.write_str("denied"),
}
}
}
impl ::std::str::FromStr for PayloadDecision {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"approved" => Ok(Self::Approved),
"denied" => Ok(Self::Denied),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for PayloadDecision {
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 PayloadDecision {
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 PayloadDecision {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///Echoed from the matching approve-request. The relying party uses it to locate the session to elevate.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Echoed from the matching approve-request. The relying party uses it to locate the session to elevate.",
/// "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())
})
}
}
///Echoed from the matching approve-request. The relying party verifies it equals the session's subject.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Echoed from the matching approve-request. The relying party verifies it equals the session's subject.",
/// "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())
})
}
}
///Acknowledgement the relying party returns after processing the approval. Carried in a Trust Task document whose type is https://trusttasks.org/spec/auth/step-up/approve-response/0.1#response.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Response",
/// "description": "Acknowledgement the relying party returns after processing the approval. Carried in a Trust Task document whose type is https://trusttasks.org/spec/auth/step-up/approve-response/0.1#response.",
/// "type": "object",
/// "required": [
/// "status"
/// ],
/// "properties": {
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// },
/// "reason": {
/// "description": "Present when status is `rejected` (e.g. \"challenge expired\", \"session not found\", \"acr ceiling exceeded\").",
/// "type": "string"
/// },
/// "session": {
/// "description": "Present when status is `elevated`. The session's updated amr/acr after the approval was applied.",
/// "$ref": "#/definitions/Session"
/// },
/// "status": {
/// "type": "string",
/// "enum": [
/// "elevated",
/// "rejected"
/// ]
/// }
/// },
/// "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>,
///Present when status is `rejected` (e.g. "challenge expired", "session not found", "acr ceiling exceeded").
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub reason: ::std::option::Option<::std::string::String>,
///Present when status is `elevated`. The session's updated amr/acr after the approval was applied.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub session: ::std::option::Option<Session>,
pub status: ResponseStatus,
}
impl ::std::convert::From<&Response> for Response {
fn from(value: &Response) -> Self {
value.clone()
}
}
///`ResponseStatus`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "string",
/// "enum": [
/// "elevated",
/// "rejected"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
pub enum ResponseStatus {
#[serde(rename = "elevated")]
Elevated,
#[serde(rename = "rejected")]
Rejected,
}
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::Elevated => f.write_str("elevated"),
Self::Rejected => f.write_str("rejected"),
}
}
}
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 {
"elevated" => Ok(Self::Elevated),
"rejected" => Ok(Self::Rejected),
_ => 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()
}
}
///A logical authentication context bound to a subject. Producers and consumers exchange Session-shaped data in challenge issuance, authentication responses, and introspection (whoami).
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Session",
/// "description": "A logical authentication context bound to a subject. Producers and consumers exchange Session-shaped data in challenge issuance, authentication responses, and introspection (whoami).",
/// "type": "object",
/// "required": [
/// "expiresAt",
/// "id",
/// "issuedAt",
/// "subject"
/// ],
/// "properties": {
/// "acr": {
/// "description": "Authentication Context Class Reference per [OIDC Core §2]. Profiles define their own values; the recommended set is \"aal1\" (single-factor DID auth), \"aal2\" (a second possession-or-biometric factor confirmed), and \"aal3\" (hardware-bound second factor).",
/// "type": "string"
/// },
/// "amr": {
/// "description": "Authentication Methods References per [RFC 8176]. Typical values: \"did\" (challenge-response), \"passkey\" (WebAuthn), \"vta\" (verifiable-trust agent approval). Multi-factor sessions list every method used.",
/// "type": "array",
/// "items": {
/// "type": "string",
/// "minLength": 1
/// },
/// "minItems": 1
/// },
/// "expiresAt": {
/// "description": "ISO-8601 timestamp when the session ceases to be valid. Producers SHOULD refresh before this time; consumers MUST reject after.",
/// "type": "string",
/// "format": "date-time"
/// },
/// "ext": {
/// "description": "Ecosystem-defined extension members per SPEC.md §4.5.1.",
/// "$ref": "#/definitions/Ext"
/// },
/// "id": {
/// "description": "Opaque, server-chosen session identifier. Stable for the lifetime of the session. Consumers MUST treat the value as opaque; no structure is implied.",
/// "type": "string",
/// "minLength": 1
/// },
/// "issuedAt": {
/// "description": "ISO-8601 timestamp when the session was created.",
/// "type": "string",
/// "format": "date-time"
/// },
/// "subject": {
/// "description": "The authenticated party's VID (typically a DID URL).",
/// "type": "string",
/// "minLength": 1
/// }
/// },
/// "additionalProperties": false,
/// "$anchor": "session"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct Session {
///Authentication Context Class Reference per [OIDC Core §2]. Profiles define their own values; the recommended set is "aal1" (single-factor DID auth), "aal2" (a second possession-or-biometric factor confirmed), and "aal3" (hardware-bound second factor).
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub acr: ::std::option::Option<::std::string::String>,
///Authentication Methods References per [RFC 8176]. Typical values: "did" (challenge-response), "passkey" (WebAuthn), "vta" (verifiable-trust agent approval). Multi-factor sessions list every method used.
#[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")]
pub amr: ::std::vec::Vec<SessionAmrItem>,
///ISO-8601 timestamp when the session ceases to be valid. Producers SHOULD refresh before this time; consumers MUST reject after.
#[serde(rename = "expiresAt")]
pub expires_at: ::chrono::DateTime<::chrono::offset::Utc>,
///Ecosystem-defined extension members per SPEC.md §4.5.1.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///Opaque, server-chosen session identifier. Stable for the lifetime of the session. Consumers MUST treat the value as opaque; no structure is implied.
pub id: SessionId,
///ISO-8601 timestamp when the session was created.
#[serde(rename = "issuedAt")]
pub issued_at: ::chrono::DateTime<::chrono::offset::Utc>,
///The authenticated party's VID (typically a DID URL).
pub subject: SessionSubject,
}
impl ::std::convert::From<&Session> for Session {
fn from(value: &Session) -> Self {
value.clone()
}
}
///`SessionAmrItem`
///
/// <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 SessionAmrItem(::std::string::String);
impl ::std::ops::Deref for SessionAmrItem {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<SessionAmrItem> for ::std::string::String {
fn from(value: SessionAmrItem) -> Self {
value.0
}
}
impl ::std::convert::From<&SessionAmrItem> for SessionAmrItem {
fn from(value: &SessionAmrItem) -> Self {
value.clone()
}
}
impl ::std::str::FromStr for SessionAmrItem {
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 SessionAmrItem {
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 SessionAmrItem {
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 SessionAmrItem {
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 SessionAmrItem {
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())
})
}
}
///Opaque, server-chosen session identifier. Stable for the lifetime of the session. Consumers MUST treat the value as opaque; no structure is implied.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Opaque, server-chosen session identifier. Stable for the lifetime of the session. Consumers MUST treat the value as opaque; no structure is implied.",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct SessionId(::std::string::String);
impl ::std::ops::Deref for SessionId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<SessionId> for ::std::string::String {
fn from(value: SessionId) -> Self {
value.0
}
}
impl ::std::convert::From<&SessionId> for SessionId {
fn from(value: &SessionId) -> Self {
value.clone()
}
}
impl ::std::str::FromStr for SessionId {
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 SessionId {
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 SessionId {
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 SessionId {
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 SessionId {
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 authenticated party's VID (typically a DID URL).
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The authenticated party's VID (typically a DID URL).",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct SessionSubject(::std::string::String);
impl ::std::ops::Deref for SessionSubject {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<SessionSubject> for ::std::string::String {
fn from(value: SessionSubject) -> Self {
value.0
}
}
impl ::std::convert::From<&SessionSubject> for SessionSubject {
fn from(value: &SessionSubject) -> Self {
value.clone()
}
}
impl ::std::str::FromStr for SessionSubject {
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 SessionSubject {
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 SessionSubject {
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 SessionSubject {
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 SessionSubject {
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())
})
}
}
impl crate::Payload for Payload {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/auth/step-up/approve-response/0.1";
}
impl crate::Payload for Response {
const TYPE_URI: &'static str =
"https://trusttasks.org/spec/auth/step-up/approve-response/0.1#response";
}
#[cfg(feature = "validate")]
impl crate::validate::ValidatedPayload for Payload {
const SCHEMA_JSON: &'static str = "{\n \"$defs\": {\n \"AssertionResponse\": {\n \"$anchor\": \"assertionResponse\",\n \"additionalProperties\": false,\n \"description\": \"The credential the client returns from `navigator.credentials.get`. Binary fields are base64url-encoded.\",\n \"properties\": {\n \"authenticatorAttachment\": {\n \"enum\": [\n \"platform\",\n \"cross-platform\"\n ]\n },\n \"clientExtensionResults\": {\n \"type\": \"object\"\n },\n \"id\": {\n \"type\": \"string\"\n },\n \"rawId\": {\n \"type\": \"string\"\n },\n \"response\": {\n \"additionalProperties\": false,\n \"properties\": {\n \"authenticatorData\": {\n \"type\": \"string\"\n },\n \"clientDataJSON\": {\n \"type\": \"string\"\n },\n \"signature\": {\n \"type\": \"string\"\n },\n \"userHandle\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n },\n \"required\": [\n \"clientDataJSON\",\n \"authenticatorData\",\n \"signature\"\n ],\n \"type\": \"object\"\n },\n \"type\": {\n \"const\": \"public-key\"\n }\n },\n \"required\": [\n \"id\",\n \"rawId\",\n \"type\",\n \"response\"\n ],\n \"title\": \"AuthenticatorAssertionResponse (login)\",\n \"type\": \"object\"\n },\n \"Evidence\": {\n \"$anchor\": \"evidence\",\n \"description\": \"Tagged union over `kind` describing the cryptographic factor the approver presents to back the elevation. Pluggable so new factor kinds can be added without disturbing existing consumers (SPEC §4.5.1 ext rules apply to unknown kinds at the producer's discretion).\",\n \"oneOf\": [\n {\n \"additionalProperties\": false,\n \"description\": \"The elevation is gated by the document's framework `proof` — a Data Integrity signature from a key the subject controls (SPEC §4.7). This is the default when `evidence` is omitted. `amr` reflects \\\"vta\\\"/\\\"did\\\".\",\n \"properties\": {\n \"kind\": {\n \"const\": \"did-signed\"\n }\n },\n \"required\": [\n \"kind\"\n ],\n \"title\": \"DidSigned\",\n \"type\": \"object\"\n },\n {\n \"additionalProperties\": false,\n \"properties\": {\n \"assertion\": {\n \"$ref\": \"#/$defs/AssertionResponse\",\n \"description\": \"The unmodified AuthenticatorAssertionResponse from the platform WebAuthn API (`navigator.credentials.get` / ASAuthorization / Credential Manager). Its `clientDataJSON` challenge MUST equal the step-up `challenge`. The relying party verifies it per WebAuthn Level 2 §7.2 exactly as auth/passkey/login/finish does; the assertion is the gate and `amr` reflects \\\"passkey\\\".\"\n },\n \"kind\": {\n \"const\": \"webauthn\"\n }\n },\n \"required\": [\n \"kind\",\n \"assertion\"\n ],\n \"title\": \"WebAuthn\",\n \"type\": \"object\"\n }\n ],\n \"title\": \"Step-up Evidence\"\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\": \"Acknowledgement the relying party returns after processing the approval. Carried in a Trust Task document whose type is https://trusttasks.org/spec/auth/step-up/approve-response/0.1#response.\",\n \"properties\": {\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"reason\": {\n \"description\": \"Present when status is `rejected` (e.g. \\\"challenge expired\\\", \\\"session not found\\\", \\\"acr ceiling exceeded\\\").\",\n \"type\": \"string\"\n },\n \"session\": {\n \"$ref\": \"#/$defs/Session\",\n \"description\": \"Present when status is `elevated`. The session's updated amr/acr after the approval was applied.\"\n },\n \"status\": {\n \"enum\": [\n \"elevated\",\n \"rejected\"\n ],\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"title\": \"Auth Step-up Approve Response — relying party ack\",\n \"type\": \"object\"\n },\n \"Session\": {\n \"$anchor\": \"session\",\n \"additionalProperties\": false,\n \"description\": \"A logical authentication context bound to a subject. Producers and consumers exchange Session-shaped data in challenge issuance, authentication responses, and introspection (whoami).\",\n \"properties\": {\n \"acr\": {\n \"description\": \"Authentication Context Class Reference per [OIDC Core §2]. Profiles define their own values; the recommended set is \\\"aal1\\\" (single-factor DID auth), \\\"aal2\\\" (a second possession-or-biometric factor confirmed), and \\\"aal3\\\" (hardware-bound second factor).\",\n \"type\": \"string\"\n },\n \"amr\": {\n \"description\": \"Authentication Methods References per [RFC 8176]. Typical values: \\\"did\\\" (challenge-response), \\\"passkey\\\" (WebAuthn), \\\"vta\\\" (verifiable-trust agent approval). Multi-factor sessions list every method used.\",\n \"items\": {\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"minItems\": 1,\n \"type\": \"array\"\n },\n \"expiresAt\": {\n \"description\": \"ISO-8601 timestamp when the session ceases to be valid. Producers SHOULD refresh before this time; consumers MUST reject after.\",\n \"format\": \"date-time\",\n \"type\": \"string\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1.\"\n },\n \"id\": {\n \"description\": \"Opaque, server-chosen session identifier. Stable for the lifetime of the session. Consumers MUST treat the value as opaque; no structure is implied.\",\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"issuedAt\": {\n \"description\": \"ISO-8601 timestamp when the session was created.\",\n \"format\": \"date-time\",\n \"type\": \"string\"\n },\n \"subject\": {\n \"description\": \"The authenticated party's VID (typically a DID URL).\",\n \"minLength\": 1,\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"id\",\n \"subject\",\n \"issuedAt\",\n \"expiresAt\"\n ],\n \"title\": \"Session\",\n \"type\": \"object\"\n }\n },\n \"$id\": \"https://trusttasks.org/spec/auth/step-up/approve-response/0.1\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"description\": \"The approver's signed ratification of a step-up: subject + sessionId + challenge are echoed inside a proof-bearing document so the relying party can elevate the session.\",\n \"properties\": {\n \"challenge\": {\n \"description\": \"Echoed from the matching approve-request. The relying party verifies it equals the bound challenge.\",\n \"minLength\": 16,\n \"type\": \"string\"\n },\n \"decision\": {\n \"description\": \"`approved` elevates the session per the relying party's policy. `denied` is a signed refusal — useful for audit even though it elevates nothing.\",\n \"enum\": [\n \"approved\",\n \"denied\"\n ],\n \"type\": \"string\"\n },\n \"deniedReason\": {\n \"description\": \"Required when decision is `denied`. Human-readable rationale the user provided (or which the approver inferred).\",\n \"type\": \"string\"\n },\n \"evidence\": {\n \"$ref\": \"#/$defs/Evidence\",\n \"description\": \"How the approver demonstrated the factor backing this elevation. A tagged union on `kind`. When `evidence` is absent the elevation is gated solely by the document's framework `proof` (equivalent to `kind: did-signed`). When `kind: webauthn` is supplied, the carried WebAuthn assertion over `challenge` is the gate and the framework `proof` MAY be omitted.\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"grantedAcr\": {\n \"description\": \"The acr the approver believes it has cryptographically demonstrated. The relying party MAY accept this, MAY upgrade to a lower value, but MUST NOT exceed it.\",\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"description\": \"Echoed from the matching approve-request. The relying party uses it to locate the session to elevate.\",\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"subject\": {\n \"description\": \"Echoed from the matching approve-request. The relying party verifies it equals the session's subject.\",\n \"minLength\": 1,\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"subject\",\n \"sessionId\",\n \"challenge\",\n \"decision\"\n ],\n \"title\": \"Auth — Step-up Approve Response\",\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\": \"approve-ack-9012-3456-7890-abcdef123456\",\n \"type\": \"https://trusttasks.org/spec/auth/step-up/approve-response/0.1#response\",\n \"threadId\": \"approve-resp-7890-1234-5678-90abcdef1234\",\n \"issuer\": \"did:web:bank.example\",\n \"recipient\": \"did:web:alice.example\",\n \"issuedAt\": \"2026-05-23T14:00:31Z\",\n \"payload\": {\n \"status\": \"elevated\",\n \"session\": {\n \"id\": \"ec5d3c89-3f49-49b2-9d7d-2a8c0a8a7b9b\",\n \"subject\": \"did:web:alice.example\",\n \"issuedAt\": \"2026-05-23T10:00:31Z\",\n \"expiresAt\": \"2026-05-23T14:30:31Z\",\n \"amr\": [\"did\", \"vta\"],\n \"acr\": \"aal2\"\n }\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\": \"approve-ack-0123-4567-8901-bcdef1234567\",\n \"type\": \"https://trusttasks.org/spec/auth/step-up/approve-response/0.1#response\",\n \"threadId\": \"approve-resp-7890-1234-5678-90abcdef1234\",\n \"issuer\": \"did:web:bank.example\",\n \"recipient\": \"did:web:alice.example\",\n \"issuedAt\": \"2026-05-23T14:00:31Z\",\n \"payload\": {\n \"status\": \"rejected\",\n \"reason\": \"challenge expired\"\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 challenge.",
"{\n \"decision\": \"approved\",\n \"sessionId\": \"ec5d3c89\",\n \"subject\": \"did:web:alice.example\"\n}",
),
(
"Decision value not in enum.",
"{\n \"challenge\": \"VHJhbnNmZXJDb25maXJtTm9uY2VYWQ\",\n \"decision\": \"maybe\",\n \"sessionId\": \"ec5d3c89\",\n \"subject\": \"did:web:alice.example\"\n}",
),
(
"Challenge below minimum length.",
"{\n \"challenge\": \"short\",\n \"decision\": \"approved\",\n \"sessionId\": \"ec5d3c89\",\n \"subject\": \"did:web:alice.example\"\n}",
),
(
"Unknown evidence kind.",
"{\n \"challenge\": \"VHJhbnNmZXJDb25maXJtTm9uY2VYWQ\",\n \"decision\": \"approved\",\n \"evidence\": {\n \"code\": \"123456\",\n \"kind\": \"totp\"\n },\n \"sessionId\": \"ec5d3c89\",\n \"subject\": \"did:web:alice.example\"\n}",
),
(
"WebAuthn evidence missing the required assertion.",
"{\n \"challenge\": \"VHJhbnNmZXJDb25maXJtTm9uY2VYWQ\",\n \"decision\": \"approved\",\n \"evidence\": {\n \"kind\": \"webauthn\"\n },\n \"sessionId\": \"ec5d3c89\",\n \"subject\": \"did:web:alice.example\"\n}",
),
(
"WebAuthn assertion response is missing required signature field.",
"{\n \"challenge\": \"VHJhbnNmZXJDb25maXJtTm9uY2VYWQ\",\n \"decision\": \"approved\",\n \"evidence\": {\n \"assertion\": {\n \"id\": \"Y3JlZF8xYTJiM2M\",\n \"rawId\": \"Y3JlZF8xYTJiM2M\",\n \"response\": {\n \"authenticatorData\": \"TXltSXNUaGVBdXRoRGF0YQ\",\n \"clientDataJSON\": \"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0In0\"\n },\n \"type\": \"public-key\"\n },\n \"kind\": \"webauthn\"\n },\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
);
}
}
}