corteq-onepassword 0.1.0

Secure 1Password SDK wrapper with FFI bindings for Rust applications
Documentation
# Architecture

This document describes the architecture of `corteq-onepassword`, a Rust wrapper around the 1Password SDK.

## High-Level Overview

```mermaid
flowchart TB
    subgraph "User Application"
        App[Application Code]
    end

    subgraph "corteq-onepassword"
        Client[OnePassword Client]
        Builder[OnePasswordBuilder]
        Secret[SecretReference / SecretMap]
        Error[Error Types]

        subgraph "FFI Layer"
            Bindings[SdkClient]
            Loader[NativeLibrary]
            Protocol[JSON Protocol]
            Types[UniFFI Types]
        end
    end

    subgraph "Native Library"
        SDK[libop_uniffi_core.so/.dylib]
    end

    subgraph "1Password Cloud"
        API[1Password API]
    end

    App --> Client
    Client --> Bindings
    Bindings --> Loader
    Loader --> SDK
    SDK --> API
```

## Module Structure

```mermaid
graph TD
    subgraph "Public API (lib.rs)"
        OnePassword
        OnePasswordBuilder
        Error
        SecretReference
        SecretMap
        ExposeSecret[ExposeSecret re-export]
        SecretString[SecretString re-export]
    end

    subgraph "Internal Modules"
        client.rs --> ffi/mod.rs
        client.rs --> secret.rs
        client.rs --> error.rs

        ffi/mod.rs --> ffi/bindings.rs
        ffi/mod.rs --> ffi/loader.rs
        ffi/bindings.rs --> ffi/protocol.rs
        ffi/bindings.rs --> ffi/uniffi_types.rs
        ffi/loader.rs --> ffi/uniffi_types.rs
    end
```

## Module Responsibilities

| Module | File | Responsibility |
|--------|------|----------------|
| **client** | `src/client.rs` | Public API, builder pattern, token validation |
| **error** | `src/error.rs` | Error types with security-safe messages |
| **secret** | `src/secret.rs` | Reference parsing, secret collections |
| **ffi** | `src/ffi/mod.rs` | FFI module root |
| **bindings** | `src/ffi/bindings.rs` | Safe Rust wrapper over raw FFI |
| **loader** | `src/ffi/loader.rs` | Dynamic library loading |
| **protocol** | `src/ffi/protocol.rs` | JSON serialization for SDK calls |
| **uniffi_types** | `src/ffi/uniffi_types.rs` | C ABI types for UniFFI |

## Data Flow: Resolving a Secret

```mermaid
sequenceDiagram
    participant App as Application
    participant Client as OnePassword
    participant Bindings as SdkClient
    participant Loader as NativeLibrary
    participant SDK as libop_uniffi_core
    participant Cloud as 1Password API

    App->>Client: secret("op://vault/item/field")
    Client->>Client: Parse SecretReference
    Client->>Client: Lock async Mutex
    Client->>Bindings: resolve_secret(reference)
    Bindings->>Bindings: Build SdkInvocation JSON
    Bindings->>Loader: invoke_sync(json)
    Loader->>SDK: FFI call with RustBuffer
    SDK->>Cloud: HTTPS request
    Cloud-->>SDK: Encrypted response
    SDK-->>Loader: RustBuffer with result
    Loader-->>Bindings: JSON string
    Bindings->>Bindings: Parse response
    Bindings-->>Client: SecretString
    Client-->>App: SecretString
```

## Connection Flow

```mermaid
sequenceDiagram
    participant App as Application
    participant Builder as OnePasswordBuilder
    participant Client as OnePassword
    participant Loader as loader.rs
    participant Bindings as SdkClient

    App->>Builder: from_env() / from_token()
    Builder->>Builder: Validate token format
    App->>Builder: .integration(name, version)
    App->>Builder: .connect()
    Builder->>Loader: load_library()
    Loader->>Loader: Find bundled library
    Loader->>Loader: dlopen() native library
    Loader->>Loader: Resolve FFI symbols
    Loader-->>Builder: Arc<NativeLibrary>
    Builder->>Bindings: SdkClient::init(library, token, ...)
    Bindings->>Bindings: Serialize InitClientParams
    Bindings->>Loader: init_client(json)
    Loader-->>Bindings: client_id
    Bindings-->>Builder: SdkClient
    Builder->>Client: Wrap in Arc<Mutex<>>
    Builder-->>App: OnePassword
```

## FFI Layer Architecture

```mermaid
flowchart TB
    subgraph "Rust (Safe)"
        SdkClient[SdkClient<br/>bindings.rs]
        Protocol[SdkInvocation<br/>protocol.rs]
    end

    subgraph "Rust (Unsafe Boundary)"
        NativeLib[NativeLibrary<br/>loader.rs]
        Types[RustBuffer / RustCallStatus<br/>uniffi_types.rs]
    end

    subgraph "Native (C ABI)"
        InitClient[uniffi_op_uniffi_core_fn_func_init_client]
        Invoke[uniffi_op_uniffi_core_fn_func_invoke]
        Release[uniffi_op_uniffi_core_fn_func_release_client]
        AllocBuf[ffi_op_uniffi_core_rustbuffer_alloc]
        FreeBuf[ffi_op_uniffi_core_rustbuffer_free]
    end

    SdkClient --> Protocol
    SdkClient --> NativeLib
    NativeLib --> Types
    NativeLib -.->|dlsym| InitClient
    NativeLib -.->|dlsym| Invoke
    NativeLib -.->|dlsym| Release
    NativeLib -.->|dlsym| AllocBuf
    NativeLib -.->|dlsym| FreeBuf
```

## Native Library Loading

```mermaid
flowchart TD
    Start([load_library called])

    Start --> CheckEnv{ONEPASSWORD_LIB_PATH<br/>set?}

    CheckEnv -->|Yes| ValidateEnv{Path exists &<br/>correct filename?}
    ValidateEnv -->|Yes| LoadCustom[Load from custom path]
    ValidateEnv -->|No| Error1[Error: Invalid path]

    CheckEnv -->|No| DetectPlatform[Detect OS + Arch]
    DetectPlatform --> BuildPath[Build bundled path:<br/>src/libs/{platform}/libop_uniffi_core.*]
    BuildPath --> CheckBundled{Bundled lib<br/>exists?}
    CheckBundled -->|Yes| LoadBundled[Load from bundled path]
    CheckBundled -->|No| Error2[Error: Library not found]

    LoadCustom --> ResolveSymbols
    LoadBundled --> ResolveSymbols

    ResolveSymbols[Resolve 5 FFI symbols]
    ResolveSymbols --> Success([Return Arc<NativeLibrary>])
```

## Thread Safety Model

```mermaid
flowchart LR
    subgraph "Thread 1"
        T1[Task 1]
    end

    subgraph "Thread 2"
        T2[Task 2]
    end

    subgraph "Thread 3"
        T3[Task 3]
    end

    subgraph "OnePassword Client"
        Arc[Arc<OnePassword>]
        Mutex[Mutex<SdkClient>]
        Client[SdkClient]
    end

    T1 --> Arc
    T2 --> Arc
    T3 --> Arc
    Arc --> Mutex
    Mutex -->|serialized access| Client
```

The client uses `Arc<Mutex<SdkClient>>` to ensure:
- **Arc**: Multiple ownership across tasks/threads
- **Mutex**: Serialized access to the underlying SDK client
- **Send + Sync**: Safe to share across thread boundaries

## Security Architecture

```mermaid
flowchart TB
    subgraph "Sensitive Data Handling"
        Token[Service Account Token]
        Secrets[Retrieved Secrets]
    end

    subgraph "Protection Mechanisms"
        SecretString[SecretString wrapper]
        Zeroize[Automatic zeroization]
        NoDebug[Redacted Debug impls]
        NoError[No secrets in errors]
    end

    Token --> SecretString
    Secrets --> SecretString
    SecretString --> Zeroize
    SecretString --> NoDebug
    SecretString --> NoError
```

### Security Guarantees

| Concern | Protection |
|---------|------------|
| Token exposure | Wrapped in `SecretString`, zeroized on drop |
| Secret logging | `Debug` impls show `[REDACTED]` |
| Error messages | Never contain token or secret values |
| Memory safety | Rust ownership + bounds checking |
| Concurrent access | `Mutex` serialization |

## Error Handling

```mermaid
flowchart TD
    subgraph "Error Sources"
        EnvVar[Missing env var]
        TokenFmt[Invalid token format]
        LibLoad[Library load failure]
        Auth[Authentication failure]
        Network[Network issues]
        NotFound[Secret not found]
        Access[Access denied]
    end

    subgraph "Error Types"
        MissingAuthToken
        InvalidToken
        LibraryLoadError
        AuthenticationFailed
        NetworkError
        SecretNotFound
        AccessDenied
        SdkError
    end

    EnvVar --> MissingAuthToken
    TokenFmt --> InvalidToken
    LibLoad --> LibraryLoadError
    Auth --> AuthenticationFailed
    Network --> NetworkError
    NotFound --> SecretNotFound
    Access --> AccessDenied
```

## Platform Support

```mermaid
flowchart LR
    subgraph "Supported"
        Linux_x64[Linux x86_64]
        Linux_arm[Linux aarch64]
        macOS_x64[macOS x86_64]
        macOS_arm[macOS aarch64]
    end

    subgraph "Native Libraries"
        so_x64[libop_uniffi_core.so<br/>linux-x86_64]
        so_arm[libop_uniffi_core.so<br/>linux-aarch64]
        dylib_x64[libop_uniffi_core.dylib<br/>macos-x86_64]
        dylib_arm[libop_uniffi_core.dylib<br/>macos-aarch64]
    end

    Linux_x64 --> so_x64
    Linux_arm --> so_arm
    macOS_x64 --> dylib_x64
    macOS_arm --> dylib_arm
```

## File Layout

```
src/
├── lib.rs              # Crate root, public exports
├── client.rs           # OnePassword, OnePasswordBuilder
├── error.rs            # Error enum, Result type alias
├── secret.rs           # SecretReference, SecretMap
└── ffi/
    ├── mod.rs          # FFI module root
    ├── bindings.rs     # SdkClient - safe FFI wrapper
    ├── loader.rs       # NativeLibrary - dynamic loading
    ├── protocol.rs     # JSON serialization types
    └── uniffi_types.rs # RustBuffer, RustCallStatus

src/libs/               # Bundled native libraries
├── linux-x86_64/
├── linux-aarch64/
├── macos-x86_64/
└── macos-aarch64/
```

## Key Design Decisions

### 1. Builder Pattern
Separates configuration from connection, allowing validation before network calls.

### 2. Async-First with Blocking Option
Primary API is async (`connect()`), with optional `connect_blocking()` for sync contexts.

### 3. FFI Isolation
All unsafe FFI code is contained in the `ffi/` module, with safe wrappers exposed to the rest of the crate.

### 4. SecretString Everywhere
All sensitive data uses `SecretString` from the `secrecy` crate for automatic zeroization.

### 5. Bundled Libraries
Native SDK libraries are bundled in the crate to simplify deployment.