Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 13 additions & 24 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 7 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "aip"
version = "0.1.0"
edition = "2024"
rust-version = "1.87"
rust-version = "1.89"

[[bin]]
name = "aip"
Expand All @@ -18,11 +18,12 @@ path = "src/lib.rs"

[dependencies]
axum = { version = "0.8" }
atproto-client = { version = "0.9.6" }
atproto-identity = { version = "0.9.6", features = ["zeroize"] }
atproto-oauth = { version = "0.9.6", features = ["zeroize"] }
atproto-oauth-axum = { version = "0.9.6", features = ["zeroize"] }
atproto-xrpcs = { version = "0.9.6" }

atproto-identity = { version = "0.11.0", features = ["lru", "zeroize", "hickory-dns"] }
atproto-oauth = { version = "0.11.0", features = ["lru", "zeroize", "hickory-dns"] }
atproto-oauth-axum = { version = "0.11.0", features = ["zeroize"] }
atproto-client = { version = "0.11.0" }
atproto-xrpcs = { version = "0.11.0", features = ["hickory-dns"] }

axum-template = { version = "3.0", features = ["minijinja"] }
minijinja = { version = "2.7", features = ["builtins"] }
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build stage
FROM rust:1.87-slim AS builder
FROM rust:1.89-slim AS builder

# Install required system dependencies for building
RUN apt-get update && apt-get install -y \
Expand Down
8 changes: 5 additions & 3 deletions src/bin/aip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use aip::{
};
use anyhow::Result;
use atproto_identity::{
resolve::{IdentityResolver, InnerIdentityResolver, create_resolver},
resolve::{HickoryDnsResolver, InnerIdentityResolver, SharedIdentityResolver},
storage::DidDocumentStorage,
storage_lru::LruDidDocumentStorage,
};
Expand Down Expand Up @@ -122,10 +122,12 @@ async fn main() -> Result<()> {
};

// Initialize the DNS resolver
let dns_resolver = create_resolver(config.dns_nameservers.as_ref());
let dns_resolver = Arc::new(HickoryDnsResolver::create_resolver(
config.dns_nameservers.as_ref(),
));

// Initialize the identity resolver
let identity_resolver = IdentityResolver(Arc::new(InnerIdentityResolver {
let identity_resolver = SharedIdentityResolver(Arc::new(InnerIdentityResolver {
dns_resolver,
http_client: http_client.clone(),
plc_hostname: config.plc_hostname.clone(),
Expand Down
17 changes: 10 additions & 7 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Environment-based configuration types for AIP server runtime settings.

use anyhow::Result;
use atproto_identity::key::{KeyData, KeyType, generate_key, identify_key};
use atproto_identity::key::{KeyData, identify_key};
use std::time::Duration;

use crate::errors::ConfigError;
Expand Down Expand Up @@ -329,13 +329,15 @@ impl TryFrom<Option<String>> for PrivateKeys {
match value {
None => {
// Generate a new P-256 private key if no keys are provided
let key = generate_key(KeyType::P256Private)?;
Ok(Self(vec![key]))
// let key = generate_key(KeyType::P256Private)?;
// Ok(Self(vec![key]))
unreachable!()
}
Some(value) if value.is_empty() => {
// Generate a new P-256 private key if no keys are provided
let key = generate_key(KeyType::P256Private)?;
Ok(Self(vec![key]))
// let key = generate_key(KeyType::P256Private)?;
// Ok(Self(vec![key]))
unreachable!()
}
Some(value) => {
// Parse semicolon-separated list of KeyData DID strings
Expand All @@ -347,8 +349,9 @@ impl TryFrom<Option<String>> for PrivateKeys {

if keys.is_empty() {
// Generate a new P-256 private key if parsing resulted in empty list
let key = generate_key(KeyType::P256Private)?;
Ok(Self(vec![key]))
// let key = generate_key(KeyType::P256Private)?;
// Ok(Self(vec![key]))
unreachable!()
} else {
Ok(Self(keys))
}
Expand Down
11 changes: 4 additions & 7 deletions src/http/context.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
//! Application state and request context management.

use atproto_identity::{
axum::state::DidDocumentStorageExtractor,
key::{KeyData, KeyProvider},
resolve::IdentityResolver,
resolve::SharedIdentityResolver,
storage::DidDocumentStorage,
};
use atproto_oauth::storage::OAuthRequestStorage;
Expand Down Expand Up @@ -41,7 +40,7 @@ pub struct AppState {
/// Template engine for rendering HTML responses.
pub template_env: AppEngine,
/// Identity resolver for ATProtocol DIDs
pub identity_resolver: IdentityResolver,
pub identity_resolver: SharedIdentityResolver,
/// Key provider for OAuth signing keys
pub key_provider: Arc<dyn KeyProvider + Send + Sync>,
/// OAuth request storage for ATProtocol flows
Expand Down Expand Up @@ -69,10 +68,8 @@ impl FromRef<AppState> for Arc<dyn DPoPNonceProvider> {
}
}

impl FromRef<AppState> for DidDocumentStorageExtractor {
impl FromRef<AppState> for Arc<dyn DidDocumentStorage> {
fn from_ref(app_state: &AppState) -> Self {
atproto_identity::axum::state::DidDocumentStorageExtractor(
app_state.document_storage.clone(),
)
app_state.document_storage.clone()
}
}
6 changes: 3 additions & 3 deletions src/http/handler_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ mod tests {
use crate::oauth::DPoPNonceGenerator;
use crate::storage::SimpleKeyProvider;
use crate::storage::inmemory::MemoryOAuthStorage;
use atproto_identity::{resolve::create_resolver, storage_lru::LruDidDocumentStorage};
use atproto_identity::{resolve::HickoryDnsResolver, storage_lru::LruDidDocumentStorage};
use atproto_oauth::storage_lru::LruOAuthRequestStorage;
use std::{num::NonZeroUsize, sync::Arc};

Expand All @@ -33,8 +33,8 @@ mod tests {

let http_client = reqwest::Client::new();
let dns_nameservers = vec![];
let dns_resolver = create_resolver(&dns_nameservers);
let identity_resolver = atproto_identity::resolve::IdentityResolver(Arc::new(
let dns_resolver = Arc::new(HickoryDnsResolver::create_resolver(&dns_nameservers));
let identity_resolver = atproto_identity::resolve::SharedIdentityResolver(Arc::new(
atproto_identity::resolve::InnerIdentityResolver {
http_client: http_client.clone(),
dns_resolver,
Expand Down
6 changes: 3 additions & 3 deletions src/http/handler_oauth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ mod tests {
use crate::oauth::DPoPNonceGenerator;
use crate::storage::SimpleKeyProvider;
use crate::storage::inmemory::MemoryOAuthStorage;
use atproto_identity::{resolve::create_resolver, storage_lru::LruDidDocumentStorage};
use atproto_identity::{resolve::HickoryDnsResolver, storage_lru::LruDidDocumentStorage};
use atproto_oauth::storage_lru::LruOAuthRequestStorage;
use std::{num::NonZeroUsize, sync::Arc};

Expand All @@ -126,8 +126,8 @@ mod tests {

let http_client = reqwest::Client::new();
let dns_nameservers = vec![];
let dns_resolver = create_resolver(&dns_nameservers);
let identity_resolver = atproto_identity::resolve::IdentityResolver(Arc::new(
let dns_resolver = Arc::new(HickoryDnsResolver::create_resolver(&dns_nameservers));
let identity_resolver = atproto_identity::resolve::SharedIdentityResolver(Arc::new(
atproto_identity::resolve::InnerIdentityResolver {
http_client: http_client.clone(),
dns_resolver,
Expand Down
29 changes: 18 additions & 11 deletions src/http/handler_par.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub(super) struct PushedAuthorizationRequest {

// ATProtocol-specific parameter (legacy, prefer login_hint)
pub subject: Option<String>,

/// JWT client assertion for private_key_jwt authentication (RFC 7523)
pub client_assertion: Option<String>,
/// Client assertion type for private_key_jwt authentication
Expand Down Expand Up @@ -284,11 +284,13 @@ fn extract_client_auth_from_headers(headers: &HeaderMap) -> Option<ClientAuthent
}

/// Extract client authentication from PAR request form data
fn extract_client_auth_from_request(request: &PushedAuthorizationRequest) -> Option<ClientAuthentication> {
fn extract_client_auth_from_request(
request: &PushedAuthorizationRequest,
) -> Option<ClientAuthentication> {
// Check for JWT client assertion first (private_key_jwt)
if let (Some(client_assertion), Some(client_assertion_type)) =
(&request.client_assertion, &request.client_assertion_type) {
if let (Some(client_assertion), Some(client_assertion_type)) =
(&request.client_assertion, &request.client_assertion_type)
{
// Validate the assertion type
if client_assertion_type == "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" {
return Some(ClientAuthentication {
Expand All @@ -299,7 +301,7 @@ fn extract_client_auth_from_request(request: &PushedAuthorizationRequest) -> Opt
});
}
}

// PAR typically uses client credentials from headers, not form data
// But we'll support client_id from the form
Some(ClientAuthentication {
Expand Down Expand Up @@ -339,28 +341,33 @@ fn authenticate_client(
ClientAuthMethod::PrivateKeyJwt => {
// Require JWT client assertion
if let Some(client_assertion) = client_auth.client_assertion.as_ref() {
// Construct token endpoint URL for audience validation
// Construct token endpoint URL for audience validation
// Note: PAR uses token endpoint as audience per RFC 9126
let token_endpoint = format!("{}/oauth/token", issuer);

// Validate the JWT client assertion
let par_endpoint = format!("{}/oauth/par", issuer);
match validate_client_assertion(client_assertion, client, &token_endpoint, Some(&par_endpoint)) {
match validate_client_assertion(
client_assertion,
client,
&token_endpoint,
Some(&par_endpoint),
) {
Ok(validated_client_id) => {
// Ensure the validated client_id matches the expected client
if validated_client_id == client.client_id {
Ok(())
} else {
Err(OAuthError::InvalidClient(
"JWT client_id does not match expected client".to_string()
"JWT client_id does not match expected client".to_string(),
))
}
}
Err(e) => Err(e),
}
} else {
Err(OAuthError::InvalidClient(
"Missing client_assertion for private_key_jwt authentication".to_string()
"Missing client_assertion for private_key_jwt authentication".to_string(),
))
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/http/handler_userinfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ mod tests {
use crate::oauth::DPoPNonceGenerator;
use crate::storage::SimpleKeyProvider;
use crate::storage::inmemory::MemoryOAuthStorage;
use atproto_identity::{resolve::create_resolver, storage_lru::LruDidDocumentStorage};
use atproto_identity::{resolve::HickoryDnsResolver, storage_lru::LruDidDocumentStorage};
use atproto_oauth::storage_lru::LruOAuthRequestStorage;
use std::{num::NonZeroUsize, sync::Arc};

Expand All @@ -127,8 +127,8 @@ mod tests {

let http_client = reqwest::Client::new();
let dns_nameservers = vec![];
let dns_resolver = create_resolver(&dns_nameservers);
let identity_resolver = atproto_identity::resolve::IdentityResolver(Arc::new(
let dns_resolver = Arc::new(HickoryDnsResolver::create_resolver(&dns_nameservers));
let identity_resolver = atproto_identity::resolve::SharedIdentityResolver(Arc::new(
atproto_identity::resolve::InnerIdentityResolver {
http_client: http_client.clone(),
dns_resolver,
Expand Down
6 changes: 4 additions & 2 deletions src/http/handler_well_known.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,10 @@ mod tests {

let http_client = reqwest::Client::new();
let dns_nameservers = vec![];
let dns_resolver = atproto_identity::resolve::create_resolver(&dns_nameservers);
let identity_resolver = atproto_identity::resolve::IdentityResolver(Arc::new(
let dns_resolver = Arc::new(
atproto_identity::resolve::HickoryDnsResolver::create_resolver(&dns_nameservers),
);
let identity_resolver = atproto_identity::resolve::SharedIdentityResolver(Arc::new(
atproto_identity::resolve::InnerIdentityResolver {
http_client: http_client.clone(),
dns_resolver,
Expand Down
Loading