mirror of
https://github.com/openai/codex.git
synced 2026-11-09 20:50:33 +00:00
## What changed - Introduce `codex-user-verification` with a provider interface for credential status, creation, deletion, and challenge signing. Include typed errors, shared cancellation guards, and hashed account-user key namespaces. - Add P-256 public-key encoding as unpadded base64url SPKI DER, derive credential IDs from its SHA-256 digest, and redact proof fields in debug output. - Add app-server helpers to validate challenge and display-text bounds and map provider errors to typed RPC errors without exposing provider diagnostics. The platform implementation reports verification as unsupported. App-server requests still return typed unavailability, with the message updated to mention build or account availability. ## Testing Add tests for credential encoding and signature verification, invalid curve points, cancellation across guard clones, stable and distinct account namespaces, and invalid challenge or display values. Update the app-server unavailability test for the revised message. GitOrigin-RevId: fe4a4eb37c68d7fdc547704e76aa257abf3e9c81
131 lines
4.6 KiB
Rust
131 lines
4.6 KiB
Rust
//! Validates local verification requests and maps native results into the app-server API.
|
|
//! Native diagnostics stay private; clients receive bounded input errors and typed reasons.
|
|
|
|
use base64::Engine as _;
|
|
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
|
use codex_app_server_protocol as rpc;
|
|
use codex_user_verification as native;
|
|
|
|
pub(super) fn validate(
|
|
params: rpc::UserVerificationVerifyParams,
|
|
) -> Result<native::UserVerificationRequest, rpc::JSONRPCErrorError> {
|
|
let invalid = || {
|
|
error(
|
|
rpc::UserVerificationErrorDetails::InvalidRequest {
|
|
reason: rpc::UserVerificationInvalidRequestReason::InvalidParams,
|
|
},
|
|
"Invalid verification challenge or display text.",
|
|
)
|
|
};
|
|
if params.challenge.len() > 5462
|
|
|| params.title.is_empty()
|
|
|| params.title.len() > 256
|
|
|| params.description.len() > 4096
|
|
{
|
|
return Err(invalid());
|
|
}
|
|
let challenge = URL_SAFE_NO_PAD
|
|
.decode(¶ms.challenge)
|
|
.map_err(|_| invalid())?;
|
|
if challenge.is_empty() || challenge.len() > 4096 {
|
|
return Err(invalid());
|
|
}
|
|
Ok(native::UserVerificationRequest {
|
|
challenge,
|
|
title: params.title,
|
|
description: params.description,
|
|
})
|
|
}
|
|
|
|
pub(crate) fn unavailable() -> rpc::JSONRPCErrorError {
|
|
error(
|
|
rpc::UserVerificationErrorDetails::Unavailable {
|
|
reason: rpc::UserVerificationUnavailableReason::ProviderUnavailable,
|
|
},
|
|
"User verification is not available in this build or account.",
|
|
)
|
|
}
|
|
|
|
pub(super) fn unavailable_reason(
|
|
reason: native::UserVerificationUnavailableReason,
|
|
) -> rpc::UserVerificationUnavailableReason {
|
|
match reason {
|
|
native::UserVerificationUnavailableReason::CredentialMissing => {
|
|
rpc::UserVerificationUnavailableReason::CredentialMissing
|
|
}
|
|
native::UserVerificationUnavailableReason::BiometricsUnavailable => {
|
|
rpc::UserVerificationUnavailableReason::BiometricsUnavailable
|
|
}
|
|
native::UserVerificationUnavailableReason::ProviderUnavailable => {
|
|
rpc::UserVerificationUnavailableReason::ProviderUnavailable
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn native_error(value: native::UserVerificationError) -> rpc::JSONRPCErrorError {
|
|
let (details, message) = match value {
|
|
native::UserVerificationError::Unavailable { reason, .. } => (
|
|
rpc::UserVerificationErrorDetails::Unavailable {
|
|
reason: unavailable_reason(reason),
|
|
},
|
|
"Local user verification is unavailable.",
|
|
),
|
|
native::UserVerificationError::Cancelled { reason, .. } => (
|
|
rpc::UserVerificationErrorDetails::Cancelled {
|
|
reason: match reason {
|
|
native::UserVerificationCancellationReason::UserCancelled => {
|
|
rpc::UserVerificationCancellationReason::UserCancelled
|
|
}
|
|
native::UserVerificationCancellationReason::Interrupted => {
|
|
rpc::UserVerificationCancellationReason::Interrupted
|
|
}
|
|
},
|
|
},
|
|
"User verification was cancelled.",
|
|
),
|
|
native::UserVerificationError::Failed { reason, .. } => (
|
|
rpc::UserVerificationErrorDetails::Failed {
|
|
reason: match reason {
|
|
native::UserVerificationFailureReason::AuthenticationFailed => {
|
|
rpc::UserVerificationFailureReason::AuthenticationFailed
|
|
}
|
|
native::UserVerificationFailureReason::Timeout => {
|
|
rpc::UserVerificationFailureReason::Timeout
|
|
}
|
|
native::UserVerificationFailureReason::ProviderError => {
|
|
rpc::UserVerificationFailureReason::ProviderError
|
|
}
|
|
},
|
|
},
|
|
"User verification failed.",
|
|
),
|
|
};
|
|
error(details, message)
|
|
}
|
|
|
|
pub(super) fn error(
|
|
data: rpc::UserVerificationErrorDetails,
|
|
message: &str,
|
|
) -> rpc::JSONRPCErrorError {
|
|
rpc::JSONRPCErrorError {
|
|
code: if matches!(
|
|
data,
|
|
rpc::UserVerificationErrorDetails::InvalidRequest { .. }
|
|
) {
|
|
-32602
|
|
} else {
|
|
-32603
|
|
},
|
|
message: message.into(),
|
|
data: Some(
|
|
serde_json::to_value(data).unwrap_or_else(
|
|
|_| serde_json::json!({"type": "failed", "reason": "providerError"}),
|
|
),
|
|
),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "user_verification_adapter_tests.rs"]
|
|
mod tests;
|