A library for ATProtocol identities.
Rust 99.5%
Dockerfile 0.5%
58 1 26

Clone this repository

https://tangled.org/smokesignal.events/atproto-identity-rs
git@tangled.org:smokesignal.events/atproto-identity-rs

For self-hosted knots, clone URLs may differ based on your setup.

README.md

atproto-identity-rs#

A comprehensive collection of Rust crates for building AT Protocol applications. This workspace provides identity management, record operations, OAuth 2.0 flows, HTTP client operations, XRPC services, and event streaming capabilities.

Note: This project contains components extracted from the open-source smokesignal.events project, an AT Protocol event and RSVP management application. This library is released under the MIT license.

Components#

Core Libraries#

  • atproto-identity - Identity management with DID resolution and cryptographic operations. Includes 4 CLI tools for identity resolution, key management, signing, and validation.
  • atproto-record - AT Protocol record signature operations. Includes 2 CLI tools for signing and verifying records.
  • atproto-oauth - OAuth 2.0 implementation with AT Protocol security extensions including PKCE, DPoP, and JWT operations.
  • atproto-oauth-aip - AT Protocol Identity Provider (AIP) OAuth workflow implementation for client applications.
  • atproto-client - HTTP client with DPoP authentication and repository operations. Includes 3 CLI tools for client authentication testing.

Web Framework Integration#

Event Streaming#

  • atproto-jetstream - WebSocket event stream consumer with compression support. Includes 1 CLI tool for event consumption.

Quick Start#

Add the crates to your Cargo.toml:

[dependencies]
atproto-identity = "0.9.0"
atproto-record = "0.9.0"
atproto-oauth = "0.9.0"
atproto-oauth-aip = "0.9.0"
atproto-client = "0.9.0"
# Add others as needed

Basic Identity Resolution#

use atproto_identity::resolve::{resolve_subject, create_resolver};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let http_client = reqwest::Client::new();
    let dns_resolver = create_resolver(&[]);
    
    let did = resolve_subject(&http_client, &dns_resolver, "alice.bsky.social").await?;
    println!("Resolved DID: {}", did);
    
    Ok(())
}

Record Signing#

use atproto_identity::key::identify_key;
use atproto_record::signature;
use serde_json::json;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let signing_key = identify_key("did:key:zQ3shNzMp4oaaQ1gQRzCxMGXFrSW3NEM1M9T6KCY9eA7HhyEA")?;
    
    let record = json!({
        "$type": "app.bsky.feed.post",
        "text": "Hello AT Protocol!",
        "createdAt": "2024-01-01T00:00:00.000Z"
    });
    
    let signature_object = json!({
        "issuer": "did:plc:issuer123",
        "issuedAt": "2024-01-01T00:00:00.000Z"
    });
    
    let signed_record = signature::create(
        &signing_key,
        &record,
        "did:plc:user123",
        "app.bsky.feed.post",
        signature_object,
    ).await?;
    
    Ok(())
}

XRPC Service#

use atproto_xrpcs::authorization::ResolvingAuthorization;
use axum::{Json, Router, extract::Query, routing::get};
use serde::Deserialize;
use serde_json::json;

#[derive(Deserialize)]
struct HelloParams {
    subject: Option<String>,
}

async fn handle_hello(
    params: Query<HelloParams>,
    authorization: Option<ResolvingAuthorization>,
) -> Json<serde_json::Value> {
    let subject = params.subject.as_deref().unwrap_or("World");
    
    let message = if let Some(auth) = authorization {
        format!("Hello, authenticated {}! (caller: {})", subject, auth.subject())
    } else {
        format!("Hello, {}!", subject)
    };
    
    Json(json!({ "message": message }))
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let app = Router::new()
        .route("/xrpc/com.example.hello", get(handle_hello))
        .with_state(your_web_context);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
    axum::serve(listener, app).await?;
    
    Ok(())
}

OAuth Client Flow#

use atproto_oauth_aip::{OAuthClient, oauth_init, oauth_complete, session_exchange};
use atproto_oauth::storage::MemoryStorage;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let client = OAuthClient::new(
        "https://your-app.com/client-id".to_string(),
        Some("your-client-secret".to_string()),
        "https://your-app.com/callback".to_string(),
    );
    
    let storage = MemoryStorage::new();
    
    // Start OAuth flow
    let (authorization_url, state) = oauth_init(
        &client,
        "alice.bsky.social",
        &storage,
    ).await?;
    
    println!("Visit: {}", authorization_url);
    
    // After callback with code...
    let access_token = oauth_complete(
        &client,
        "auth-code",
        "state",
        &storage,
    ).await?;
    
    // Get AT Protocol session
    let session = session_exchange(&client, &access_token, &storage).await?;
    println!("Authenticated as: {} ({})", session.handle, session.did);
    
    Ok(())
}

Command Line Tools#

The workspace includes 12 command-line tools across the crates. All CLI tools require the clap feature:

# Build with CLI support
cargo build --features clap --bins

# Identity operations (atproto-identity crate)
cargo run --features clap --bin atproto-identity-resolve -- alice.bsky.social
cargo run --features clap --bin atproto-identity-key -- generate p256
cargo run --features clap --bin atproto-identity-sign -- did:key:... data.json
cargo run --features clap --bin atproto-identity-validate -- did:key:... data.json signature

# Record operations (atproto-record crate)
cargo run --features clap --bin atproto-record-sign -- did:key:... did:plc:issuer record.json repository=did:plc:user collection=app.bsky.feed.post
cargo run --features clap --bin atproto-record-verify -- did:plc:issuer did:key:... signed_record.json repository=did:plc:user collection=app.bsky.feed.post

# Client operations (atproto-client crate)
cargo run --features clap --bin atproto-client-auth -- login alice.bsky.social password123
cargo run --features clap --bin atproto-client-app-password -- alice.bsky.social access_token /xrpc/com.atproto.repo.listRecords
cargo run --features clap --bin atproto-client-dpop -- alice.bsky.social did:key:... access_token /xrpc/com.atproto.repo.listRecords

# OAuth operations (atproto-oauth-axum crate)  
cargo run --features clap --bin atproto-oauth-tool -- login did:key:... alice.bsky.social

# XRPC service (atproto-xrpcs-helloworld crate)
cargo run --bin atproto-xrpcs-helloworld

# Event streaming (atproto-jetstream crate)
cargo run --features clap --bin atproto-jetstream-consumer -- jetstream1.us-east.bsky.network dictionary.zstd

Development#

# Build all crates
cargo build

# Run all tests  
cargo test

# Format and lint
cargo fmt && cargo clippy

# Generate documentation
cargo doc --workspace --open

License#

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments#

This library was extracted from the smokesignal.events project, an open-source AT Protocol event and RSVP management application.