mirror of
https://github.com/narrrl/proton-sdk-rs.git
synced 2026-11-03 16:41:26 +01:00
updated readme
This commit is contained in:
1 parent
0221a654c2
commit
ec7de59851
2 files changed
+184
-80
No files matched your search
Generated
+2
-2
@@ -1874,7 +1874,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proton-drive-rs"
|
name = "proton-drive-rs"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -1897,7 +1897,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proton-sdk"
|
name = "proton-sdk"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
|||||||
@@ -1,135 +1,239 @@
|
|||||||
# proton-sdk-rs
|
# proton-sdk-rs
|
||||||
|
|
||||||
A pure-Rust reimplementation of the [Proton Drive SDK](https://github.com/ProtonDriveApps/sdk).
|
A pure-Rust reimplementation of the [Proton Drive SDK](https://github.com/ProtonDriveApps/sdk).
|
||||||
No dependency on the official NativeAOT core — it talks to the Proton Drive API
|
|
||||||
and does the OpenPGP crypto directly. The canonical reference is the upstream
|
|
||||||
**C#** implementation; behavior is matched to it where the protocol is unclear.
|
|
||||||
|
|
||||||
> Unofficial, third-party project. Not affiliated with or endorsed by Proton.
|
This repository contains two primary crates that replicate the functionality of the official Proton SDKs (available locally in the [sdk/](file:///home/narl/dev/private/proton-sdk-rs/sdk) submodule). Rather than relying on FFI bindings to the official C# NativeAOT core, `proton-sdk-rs` is a **pure-Rust** implementation. It communicates directly with the Proton Drive API over HTTP and performs all OpenPGP cryptographic operations natively using the [`pgp` (rPGP)](https://crates.io/crates/pgp) crate.
|
||||||
> If you build on it, follow the operational requirements in the upstream SDK
|
|
||||||
> README (honest `x-pm-appversion`, event-based sync, no Proton branding, etc.).
|
|
||||||
|
|
||||||
## Status
|
> [!NOTE]
|
||||||
|
> This is an unofficial, third-party project and is not affiliated with or endorsed by Proton.
|
||||||
|
> Applications building on top of this SDK must adhere to the operational guidelines of the upstream SDK (such as setting an honest `x-pm-appversion` header, using event-based sync, and avoiding Proton branding). Detailed requirements are documented in the upstream [sdk/README.md](file:///home/narl/dev/private/proton-sdk-rs/sdk/README.md).
|
||||||
|
|
||||||
**Milestone 2 — authenticated read/write.** Implemented:
|
---
|
||||||
|
|
||||||
- **Core / HTTP**: typed IDs, API response envelope, config, reqwest client with
|
## Proven in Action
|
||||||
`x-pm-uid` / `x-pm-appversion` / bearer auth, transparent 401 refresh,
|
|
||||||
retry/backoff (408/429/502/503/504 + transient transport, `Retry-After` +
|
|
||||||
jitter), and per-request telemetry.
|
|
||||||
- **Session**: SRP-6a password login (`begin`), TOTP 2FA
|
|
||||||
(`apply_second_factor_code`), scope refresh, `resume` from tokens, `end`.
|
|
||||||
- **Account**: user keys, address keys, bcrypt key-passphrase derivation,
|
|
||||||
public-key resolution for authorship verification (`core/v4/keys/all`).
|
|
||||||
- **Drive read**: `get_my_files_folder`, `get_node`, `enumerate_folder_children`,
|
|
||||||
`enumerate_nodes`, `enumerate_trash`, with share/node decryption and recursive
|
|
||||||
parent-key resolution.
|
|
||||||
- **Download**: `download_file` / `download_file_to` (streaming), thumbnails,
|
|
||||||
content-manifest integrity + non-fatal signature verification.
|
|
||||||
- **Upload**: `upload_file` / `upload_file_from` (streaming `Read`), new
|
|
||||||
revisions, caller-supplied thumbnails, and AEAD blocks (SEIPDv2 / AES-256-GCM)
|
|
||||||
alongside legacy SEIPDv1.
|
|
||||||
- **Folder / node ops**: create, rename, trash, restore, delete, empty-trash,
|
|
||||||
and same-volume `move_node`.
|
|
||||||
- **Events**: `main_volume_id`, `latest_event_id`, `enumerate_events`.
|
|
||||||
- **Photos**: `ProtonPhotosClient` — timeline, photo get/download, upload.
|
|
||||||
- **Caching**: pluggable entity cache (`with_entity_cache`); secrets stay
|
|
||||||
in-memory.
|
|
||||||
- **Telemetry**: pluggable `Telemetry` observer (`with_telemetry`).
|
|
||||||
|
|
||||||
Not yet implemented: cross-volume move (needs `NewShareID` + re-signing),
|
This SDK is the core engine powering the following real-world projects:
|
||||||
sharing, signature-verification *enforcement* (it is non-fatal metadata).
|
* **[proton-drive-linux](https://github.com/narrrl/proton-drive-linux)**: A native client and daemon for mounting and syncing Proton Drive on Linux systems, utilizing both crates provided by this repository.
|
||||||
|
|
||||||
All crypto paths have offline round-trip tests, and the read/write surface
|
---
|
||||||
(upload/download round-trips, AEAD + streaming + thumbnails, enumeration,
|
|
||||||
folder/node ops, move, and the event feed) is covered by a **live integration
|
|
||||||
test suite** validated against a real Proton account — see
|
|
||||||
`crates/proton-drive-rs/tests/live_*.rs`.
|
|
||||||
|
|
||||||
## Workspace
|
## Workspace Structure
|
||||||
|
|
||||||
| Crate | Purpose |
|
The workspace is divided into two crates, mirroring the separation of concerns in the upstream C# codebase:
|
||||||
| --- | --- |
|
|
||||||
| `crates/proton-sdk` | Core account/session/crypto (`Proton.Sdk` analogue) |
|
|
||||||
| `crates/proton-drive-rs` | High-level Drive client (`Proton.Drive.Sdk` analogue) |
|
|
||||||
|
|
||||||
OpenPGP is provided by the [`pgp`](https://crates.io/crates/pgp) crate (rPGP 0.16).
|
| Crate | Analogue | Description | Key Modules / Entrypoint |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| **[crates/proton-sdk](file:///home/narl/dev/private/proton-sdk-rs/crates/proton-sdk)** | `Proton.Sdk` | Foundational account, session, HTTP client, and OpenPGP cryptography. | [`ProtonApiSession`](file:///home/narl/dev/private/proton-sdk-rs/crates/proton-sdk/src/session.rs) |
|
||||||
|
| **[crates/proton-drive-rs](file:///home/narl/dev/private/proton-sdk-rs/crates/proton-drive-rs)** | `Proton.Drive.Sdk` | High-level Drive & Photos client operations, folders/links management, uploads/downloads, events, caching. | [`ProtonDriveClient`](file:///home/narl/dev/private/proton-sdk-rs/crates/proton-drive-rs/src/client.rs), [`ProtonPhotosClient`](file:///home/narl/dev/private/proton-sdk-rs/crates/proton-drive-rs/src/photos.rs) |
|
||||||
|
|
||||||
## The mailbox-password requirement
|
OpenPGP features are powered by rPGP (version `0.20`), and HTTP requests are built on `reqwest`.
|
||||||
|
|
||||||
Proton's key model is layered, so **read-only decryption still needs the user's
|
---
|
||||||
mailbox (data) password**, not just session tokens:
|
|
||||||
|
## Current Feature & Parity Status
|
||||||
|
|
||||||
|
`proton-sdk-rs` implements **Milestone 2 (Authenticated Read/Write)**. Parity with the official C# SDK (pinned at the upstream commit documented in [UPSTREAM_SYNC.md](file:///home/narl/dev/private/proton-sdk-rs/UPSTREAM_SYNC.md)) is actively maintained.
|
||||||
|
|
||||||
|
### Feature Matrix
|
||||||
|
|
||||||
|
| Module | Feature | Rust Status | Notes / Upstream Parity |
|
||||||
|
| :--- | :--- | :---: | :--- |
|
||||||
|
| **Session** | SRP-6a Login | ✅ | Password-based authentication via `begin` / SRP-6a proofs. |
|
||||||
|
| | TOTP 2FA | ✅ | Support for applying second-factor TOTP codes. |
|
||||||
|
| | Token Refresh | ✅ | Scope refresh and transparent 401 token refresh. |
|
||||||
|
| | Resume Session | ✅ | Restore clients instantly via serialized tokens. |
|
||||||
|
| **HTTP Client** | Retries & Jitter | ✅ | Exponential backoff for 408, 429, and 5xx, respecting `Retry-After`. |
|
||||||
|
| | Telemetry | ✅ | Pluggable request tracking (`ITelemetry` analogue). |
|
||||||
|
| **Drive / Volume** | Volume Resolution | ✅ | Auto-resolves volume lists; auto-creates the `my-files` root volume on first login. |
|
||||||
|
| | Node Listing | ✅ | `get_node`, `enumerate_folder_children`, `enumerate_trash` with key resolution. |
|
||||||
|
| | File Operations | ✅ | Rename, trash, restore, delete, empty trash. |
|
||||||
|
| | Move Operations | ✅ | Same-volume move. Batch moves are chunked automatically. |
|
||||||
|
| | Cross-volume Move | ❌ | Not supported (throwing `NotImplementedException` in the C# public API too). |
|
||||||
|
| | Sharing | ❌ | Sharing APIs are omitted (absent in C# public API). |
|
||||||
|
| **Uploads** | Block Uploading | ✅ | Encrypts and uploads chunks (4 MiB default) to block storage. |
|
||||||
|
| | Streaming Upload | ✅ | Streams uploads from any type implementing `std::io::Read`. |
|
||||||
|
| | Revision Control | ✅ | Create drafts, upload blocks, and seal new file revisions. |
|
||||||
|
| | AEAD Block Support | ✅ | SEIPDv2 / AES-256-GCM AEAD encryption alongside legacy SEIPDv1 (AES-256-CFB). |
|
||||||
|
| | Inline Thumbnails | ✅ | Generates and encrypts inline-signed thumbnails. |
|
||||||
|
| **Downloads** | Block Downloading | ✅ | Streams block retrieval, decryption, and signature checks. |
|
||||||
|
| | Content Integrity | ✅ | Verification of file manifests and block-level SHA-256 digests. |
|
||||||
|
| | Signature Verification | ✅ | Detached signature verification (non-fatal, reports status). |
|
||||||
|
| **Photos** | Photos Timeline | ✅ | `ProtonPhotosClient` maps photostream, timeline enumeration, and photo downloads. |
|
||||||
|
| | Photo Uploads | ✅ | Uploading photos with `PhotoUploadMetadata` (capture time, tags, grouping). |
|
||||||
|
| | Photos Volume Create| ❌ | Volume creation is not yet ported. |
|
||||||
|
| **Caching** | Pluggable Cache | ✅ | Pluggable entity cache (`with_entity_cache`); keys/secrets remain strictly in memory. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deep Dive: Cryptographic Implementation
|
||||||
|
|
||||||
|
Proton Drive relies on client-side zero-knowledge encryption. The security architecture is built on a layered key model.
|
||||||
|
|
||||||
|
### 1. Layered Key Hierarchy
|
||||||
|
|
||||||
|
To perform any read or write operation, you must supply the user's **mailbox (data) password** in addition to the API session tokens. Decryption cascades as follows:
|
||||||
|
|
||||||
```
|
```
|
||||||
mailbox password + key salt --bcrypt--> key passphrase
|
[Mailbox Password] + [Key Salt]
|
||||||
-> unlock user keys -> decrypt address-key token -> unlock address key
|
│
|
||||||
-> decrypt share/node passphrase -> unlock node key -> decrypt names
|
▼ (bcrypt key-passphrase derivation)
|
||||||
|
[Key Passphrase]
|
||||||
|
│
|
||||||
|
▼ (unlocks)
|
||||||
|
[User Private Keys]
|
||||||
|
│
|
||||||
|
▼ (decrypts)
|
||||||
|
[Address Key Token] ──► [Address Private Key]
|
||||||
|
│
|
||||||
|
▼ (decrypts)
|
||||||
|
[Share Passphrase]
|
||||||
|
│
|
||||||
|
▼ (unlocks)
|
||||||
|
[Share/Node Private Key]
|
||||||
|
│
|
||||||
|
├──────────────────────────────┐
|
||||||
|
▼ (decrypts metadata/names) ▼ (decrypts content packet)
|
||||||
|
[Decrypted File Name] [Content Key (Symmetric AES-256)]
|
||||||
|
│
|
||||||
|
▼ (decrypts)
|
||||||
|
[File Ciphertext Blocks]
|
||||||
```
|
```
|
||||||
|
|
||||||
`ProtonApiSession::begin` / `resume` handle the tokens; `ProtonDriveClient::new`
|
1. **Passphrase Derivation**: The user's mailbox password is derived into a key passphrase via `bcrypt` using salts fetched from the API. See [derive.rs](file:///home/narl/dev/private/proton-sdk-rs/crates/proton-sdk/src/crypto/derive.rs).
|
||||||
takes the mailbox password.
|
2. **User & Address Keys**: The key passphrase unlocks the primary user keys. These keys are used to decrypt the tokens representing the user's address keys. Once unlocked, the address keys allow verification of signatures and unlocking of share keys. See [keys.rs](file:///home/narl/dev/private/proton-sdk-rs/crates/proton-sdk/src/crypto/keys.rs).
|
||||||
|
3. **Share & Node Keys**: Nodes (files and folders) belong to shares. A share's passphrase is decrypted by the address key. The decrypted share passphrase is used to unlock the node's private key (usually X25519/Ed25519 or legacy RSA).
|
||||||
|
4. **File Content Keys**: Files contain metadata and data blocks. The file's name and passphrase are encrypted. The symmetric **Content Key** (AES-256) is encapsulated in a PGP Public-Key Encrypted Session-Key (PKESK) packet addressed to the node key. See [content.rs](file:///home/narl/dev/private/proton-sdk-rs/crates/proton-sdk/src/crypto/content.rs).
|
||||||
|
|
||||||
## Usage
|
### 2. Download Pipeline
|
||||||
|
|
||||||
SRP login (with 2FA), then read the my-files root:
|
When streaming a file download via `download_file` or `download_file_to`:
|
||||||
|
|
||||||
|
1. **Resolve and Decrypt Content Key**: The PKESK packet `ContentKeyPacket` is retrieved from the node metadata and decrypted using the unlocked node private key, yielding the [`ContentKey`](file:///home/narl/dev/private/proton-sdk-rs/crates/proton-sdk/src/crypto/content.rs) (the symmetric session key).
|
||||||
|
2. **Retrieve Block List**: The client requests the active revision block list. Each block is represented by an absolute URL (`BareURL`) and requires a one-time `pm-storage-token` header (bearer auth is not sent to block storage hosts).
|
||||||
|
3. **Stream & Decrypt Blocks**:
|
||||||
|
- The block's ciphertext is downloaded.
|
||||||
|
- For **legacy files** (SEIPDv1): The ciphertext is decrypted using the content key in AES-256-CFB mode with MDC verification.
|
||||||
|
- For **AEAD files** (SEIPDv2): The ciphertext is decrypted using the content key in AES-256-GCM mode with 128 KiB chunk sizes.
|
||||||
|
4. **Integrity & Signature Verification**:
|
||||||
|
- The client computes the SHA-256 hash of each ciphertext block and compares it against the digests specified in the file's **Content Manifest**.
|
||||||
|
- The manifest's signature (`ManifestSignature`) is verified. The public keys for verification are resolved dynamically from the author's address (via `core/v4/keys/all`).
|
||||||
|
- Signature verification is non-fatal: a verification failure yields a [`VerificationStatus`](file:///home/narl/dev/private/proton-sdk-rs/crates/proton-sdk/src/crypto/verify.rs) but does not block read access. See [verify.rs](file:///home/narl/dev/private/proton-sdk-rs/crates/proton-sdk/src/crypto/verify.rs).
|
||||||
|
|
||||||
|
### 3. Upload Pipeline
|
||||||
|
|
||||||
|
When uploading a file:
|
||||||
|
|
||||||
|
1. **Generate Node & Content Keys**: The client generates a fresh node keypair (Ed25519/X25519) and a random AES-256 content key.
|
||||||
|
2. **Prepare Draft**:
|
||||||
|
- The node passphrase is encrypted under the parent folder's key.
|
||||||
|
- The file name is encrypted and hashed (HMAC-SHA256) under the parent's keys.
|
||||||
|
- The content key packet is generated by encrypting the content key to the file's node key.
|
||||||
|
- A draft revision is registered via `POST v2/volumes/{vid}/files`.
|
||||||
|
3. **Upload Blocks**:
|
||||||
|
- Files are split into blocks (typically 4 MiB).
|
||||||
|
- Each block is encrypted into a PGP SEIPD packet (either SEIPDv1 or SEIPDv2/AEAD).
|
||||||
|
- A detached plaintext signature is generated for the block and encrypted to the file's node key.
|
||||||
|
- The encrypted block is POSTed to the storage endpoint.
|
||||||
|
4. **Seal Revision**:
|
||||||
|
- Once all blocks are uploaded, the client compiles the Content Manifest (concatenated SHA-256 block hashes + encrypted XAttr metadata block).
|
||||||
|
- The manifest is signed using the address key (`ManifestSignature`).
|
||||||
|
- The revision is closed and finalized using a `PUT` request. See [client.rs](file:///home/narl/dev/private/proton-sdk-rs/crates/proton-drive-rs/src/client.rs) for the upload orchestration logic.
|
||||||
|
|
||||||
|
### 4. Same-Volume Move Cryptography
|
||||||
|
|
||||||
|
Moving a file or folder within the same volume is an offline cryptographic operation that avoids re-uploading file data.
|
||||||
|
- **Passphrase Rewrapping**: The node's passphrase must be re-encrypted from the source parent's key to the destination parent's key. `proton-sdk-rs` performs this by decrypting the passphrase session key and re-encrypting it ([`rewrap_message_to`](file:///home/narl/dev/private/proton-sdk-rs/crates/proton-sdk/src/crypto/content.rs)), creating a new detached `NodePassphraseSignature`. See [content.rs](file:///home/narl/dev/private/proton-sdk-rs/crates/proton-sdk/src/crypto/content.rs).
|
||||||
|
- **Metadata Update**: The file name is re-encrypted under the destination's name key, and the name's search hash is recalculated under the destination's hash key. These are submitted via the batch move endpoint.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
The following code illustrates performing an SRP login, resolving the `My Files` root, and listing its immediate children.
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
use proton_sdk::config::ProtonClientConfiguration;
|
use proton_sdk::config::ProtonClientConfiguration;
|
||||||
use proton_sdk::session::ProtonApiSession;
|
use proton_sdk::session::ProtonApiSession;
|
||||||
use proton_drive_rs::ProtonDriveClient;
|
use proton_drive_rs::ProtonDriveClient;
|
||||||
|
|
||||||
async fn run() -> proton_sdk::error::Result<()> {
|
#[tokio::main]
|
||||||
|
async fn main() -> proton_sdk::error::Result<()> {
|
||||||
|
// 1. Configure client with honest identification
|
||||||
let config = ProtonClientConfiguration::new("external-drive-myapp@0.1.0-alpha");
|
let config = ProtonClientConfiguration::new("external-drive-myapp@0.1.0-alpha");
|
||||||
|
|
||||||
|
// 2. Perform SRP Login
|
||||||
let mut session =
|
let mut session =
|
||||||
ProtonApiSession::begin(config, "user@proton.me", b"mailbox-password").await?;
|
ProtonApiSession::begin(config, "username@proton.me", b"mailbox-password").await?;
|
||||||
|
|
||||||
|
// Handle TOTP 2FA if required
|
||||||
if session.is_waiting_for_second_factor() {
|
if session.is_waiting_for_second_factor() {
|
||||||
session.apply_second_factor_code("123456").await?;
|
session.apply_second_factor_code("123456").await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Initialize Drive Client
|
||||||
let drive = ProtonDriveClient::new(&session, b"mailbox-password".to_vec());
|
let drive = ProtonDriveClient::new(&session, b"mailbox-password".to_vec());
|
||||||
|
|
||||||
|
// 4. Resolve My Files root
|
||||||
let root = drive.get_my_files_folder().await?;
|
let root = drive.get_my_files_folder().await?;
|
||||||
for child in drive.enumerate_folder_children(&root.uid).await? {
|
|
||||||
println!("{} ({:?})", child.name, child.kind);
|
// 5. Enumerate children uids and fetch their node data
|
||||||
|
let child_uids = drive.enumerate_folder_children_node_uids(&root.uid).await?;
|
||||||
|
let children = drive.enumerate_nodes(&child_uids).await?;
|
||||||
|
|
||||||
|
for child in children {
|
||||||
|
println!("Node: {} | Type: {:?}", child.name, child.kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Already have tokens? Use `ProtonApiSession::resume(config, ResumeParameters {..})`
|
If you have pre-saved session tokens, you can skip the login flow and resume the session immediately using `ProtonApiSession::resume`.
|
||||||
instead of `begin`.
|
|
||||||
|
|
||||||
A live smoke test lives in `crates/proton-drive-rs/examples/live_login.rs`
|
A live smoke test exists in [live_login.rs](file:///home/narl/dev/private/proton-sdk-rs/crates/proton-drive-rs/examples/live_login.rs) (which reads credentials from a local `.env` and `PROTON_TOTP_SECRET` env var):
|
||||||
(reads `username` / `password` from a repo-root `.env`, TOTP from
|
|
||||||
`PROTON_TOTP_SECRET`):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
PROTON_TOTP_SECRET=... cargo run -p proton-drive-rs --example live_login
|
PROTON_TOTP_SECRET=... cargo run -p proton-drive-rs --example live_login
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build & test
|
---
|
||||||
|
|
||||||
|
## Development & Testing
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
Ensure you have Rust installed (tested with Rust 1.75+).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo build # 0 warnings / 0 errors
|
cargo build
|
||||||
cargo test # offline crypto + derivation round-trip tests
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `crates/proton-drive-rs/tests/live_*.rs` suite runs the read/write surface
|
### Running Tests
|
||||||
against a real account. These tests are `#[ignore]`d by default (they need
|
|
||||||
credentials) and each cleans up after itself. With `.env` (`username` /
|
Unit tests verify the offline SRP math, key derivation, and block cryptosystems:
|
||||||
`password`) and `PROTON_TOTP_SECRET` set:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# all live tests, single-threaded (shared test account)
|
cargo test
|
||||||
PROTON_TOTP_SECRET=... cargo test -p proton-drive-rs --test 'live_*' \
|
|
||||||
-- --ignored --nocapture --test-threads=1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: AEAD upload (SEIPDv2) is **server-gated** behind the
|
### Live Integration Tests
|
||||||
`DriveCryptoEncryptBlocksWithPgpAead` feature flag; that test no-ops when the
|
|
||||||
flag is off for the account.
|
The live test suite runs read, write, upload, download, and event sync flows against a real Proton account. These tests are ignored by default. To run them, create a `.env` file at the root containing:
|
||||||
|
|
||||||
|
```env
|
||||||
|
username=your_test_user@proton.me
|
||||||
|
password=your_mailbox_password
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run tests sequentially using a single thread (to prevent TOTP overlap and anti-abuse limits)
|
||||||
|
PROTON_TOTP_SECRET=your_base32_totp_secret cargo test -p proton-drive-rs --test 'live_*' -- --ignored --nocapture --test-threads=1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT, matching upstream. The MIT license covers this source only; use of Proton's
|
This project is licensed under the MIT License, matching the license of the upstream SDK. See [LICENSE](file:///home/narl/dev/private/proton-sdk-rs/LICENSE) for details. Use of Proton's hosted services remains subject to Proton's terms of service.
|
||||||
hosted services remains subject to Proton's terms.
|
|
||||||
Reference in new issue
Block a user