Skip to content

Commit 815031d

Browse files
feat: add OneDrive and Google Drive backends via OAuth2 device code flow
Adds two new storage backends that target the substantial free tiers of consumer cloud accounts (1 TB on OneDrive with O365, 15 GB on personal Google Drive), making Enigma viable for personal backups without paying for object storage. Both providers authenticate via OAuth2 device code flow — the user enters an 8-character code on microsoft.com/devicelogin or google.com/device once, and refresh tokens stored AES-256-GCM-encrypted on disk handle every subsequent operation transparently. New module: enigma-storage/src/oauth/ - device_flow.rs: hand-rolled OAuth2 device code + refresh implementation that handles both Microsoft Graph and Google identity endpoints - token_store.rs: encrypted token persistence at <config_dir>/oauth_tokens.enc (Argon2id + HKDF-SHA256(info="enigma-oauth-v1") + AES-256-GCM, same passphrase as the keystore) - retry.rs: with_retry helper honoring HTTP 429 + Retry-After New backends: - onedrive.rs: Microsoft Graph /me/drive/special/approot endpoints with Files.ReadWrite.AppFolder scope (isolated to /Apps/Enigma/). Inline PUT for chunks ≤ 4 MiB; upload session for larger chunks (10 MiB segments) since FastCDC can produce ~8 MiB. - gdrive.rs: Drive v3 with drive.file scope (app sees only its own files). Flat folder strategy + in-memory chunk-key → file-id cache bulk-populated on first use. New CLI subcommand: - enigma auth login {onedrive,gdrive} --client-id <id> [--client-secret <s>] - enigma auth status - enigma auth logout <provider> Config: existing [[providers]] schema; type = "onedrive" or "gdrive"; bucket field unused (OneDrive uses approot, Drive uses fixed "enigma-chunks" folder). Wiring: - ProviderType::Onedrive, ProviderType::Gdrive added to enigma-core - enigma-cli/commands/providers.rs: lazy-open the encrypted token store only when an OAuth provider is configured; bubble a clear error if no passphrase is available - backup / restore / verify / gc updated to thread base_dir + passphrase through init_providers Tests: - Unit tests for retry backoff, retry-after parsing, token store roundtrip + wrong-passphrase rejection, OneDrive URL path encoding, Drive name flattening, multipart boundary uniqueness Out of scope (follow-ups): - enigma-proxy dispatch (S3 gateway) for OneDrive/GDrive — currently bails through the existing `_ => bail` arm - README translations (13 other languages) - Multi-account-per-provider support Note: this commit was not validated locally because the dev environment lacks build-essential / gcc. CI will catch any issues; follow-up commits will fix compile errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3aa6193 commit 815031d

21 files changed

Lines changed: 2197 additions & 18 deletions

File tree

Cargo.lock

Lines changed: 104 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ azure_storage = "0.20"
7777
azure_storage_blobs = "0.20"
7878
google-cloud-storage = "0.22"
7979

80+
# HTTP (for OAuth + REST backends like OneDrive / Google Drive)
81+
reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "rustls-tls"] }
82+
url = "2"
83+
urlencoding = "2"
84+
webbrowser = "1"
85+
8086
# Vault SDKs
8187
azure_security_keyvault_secrets = "0.10"
8288
azure_identity = "0.31"

README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,88 @@ addr = "enigma-2.enigma:9000"
290290
| S3-compatible | `S3Compatible`, `minio`, `rustfs`, `garage` | Requires `endpoint_url`, `path_style = true` |
291291
| Azure Blob Storage | `Azure` | `bucket` = container name |
292292
| Google Cloud Storage | `Gcs` | Uses Application Default Credentials |
293+
| Microsoft OneDrive | `Onedrive` | OAuth2 device code — see [OneDrive setup](#onedrive-setup) |
294+
| Google Drive | `Gdrive` | OAuth2 device code — see [Google Drive setup](#google-drive-setup) |
295+
296+
### OneDrive setup
297+
298+
OneDrive uses OAuth2 (device code flow). The app stores chunks in a dedicated, app-isolated folder at `/Apps/Enigma/` — it has no access to the rest of your OneDrive.
299+
300+
**One-time app registration (~5 minutes):**
301+
302+
1. Sign in to [Microsoft Entra ID admin center](https://portal.azure.com/).
303+
2. **App registrations → New registration**.
304+
3. Name: `enigma-backup` (anything). Supported account types: **"Accounts in any organizational directory and personal Microsoft accounts"**.
305+
4. Leave "Redirect URI" blank — device code flow does not use a redirect.
306+
5. Click **Register**. Copy the **Application (client) ID** that appears.
307+
6. In the new app: **Authentication** tab → "Allow public client flows" → **Yes** → Save.
308+
7. **API permissions****Add a permission** → Microsoft Graph → **Delegated permissions**
309+
- `Files.ReadWrite.AppFolder`
310+
- `offline_access`
311+
312+
**One-time login (~30 seconds):**
313+
314+
```bash
315+
enigma auth login onedrive --client-id <YOUR_CLIENT_ID>
316+
```
317+
318+
The CLI prints a URL and an 8-character code. Open the URL in any browser, paste the code, sign in to your Microsoft account, click **Yes** to grant access. Tokens are then saved AES-256-GCM-encrypted in `<config_dir>/oauth_tokens.enc` (using your Enigma passphrase) and Enigma can use them indefinitely (refresh tokens are valid for ~90 days of inactivity).
319+
320+
**Use it:**
321+
322+
```toml
323+
# Add to enigma.toml
324+
[[providers]]
325+
name = "my-onedrive"
326+
type = "onedrive"
327+
bucket = "" # unused for OneDrive
328+
weight = 1
329+
```
330+
331+
Then `enigma backup ./photos` and chunks go to `https://onedrive.live.com/?id=Apps/Enigma`.
332+
333+
### Google Drive setup
334+
335+
Google Drive also uses OAuth2 (device code flow), scope `drive.file` — the app can only see files it creates. Chunks live in a single folder named `enigma-chunks` at the user's Drive root.
336+
337+
**One-time app registration (~5 minutes):**
338+
339+
1. Open [Google Cloud Console](https://console.cloud.google.com/), create a new project (e.g. `enigma-backup`).
340+
2. **APIs & Services → Library** → enable **Google Drive API**.
341+
3. **APIs & Services → OAuth consent screen** → User Type: **External** → fill the minimum fields → Save (Test users: add your own Gmail).
342+
4. **APIs & Services → Credentials → Create credentials → OAuth client ID** → Application type: **TVs and Limited Input devices**.
343+
5. Copy the **Client ID** and **Client secret**. (Google requires a client secret in device flow even for "public" desktop clients.)
344+
345+
**One-time login (~30 seconds):**
346+
347+
```bash
348+
enigma auth login gdrive --client-id <ID> --client-secret <SECRET>
349+
```
350+
351+
Same UX as OneDrive — open the URL on any device, enter the code, grant access. Tokens saved encrypted; valid as long as the refresh token works (~6 months of inactivity for Google).
352+
353+
**Use it:**
354+
355+
```toml
356+
[[providers]]
357+
name = "my-gdrive"
358+
type = "gdrive"
359+
bucket = "" # unused for Google Drive
360+
weight = 1
361+
```
362+
363+
### OAuth token management
364+
365+
| Command | What it does |
366+
|---------|--------------|
367+
| `enigma auth login onedrive --client-id <id>` | Run the device code flow for OneDrive |
368+
| `enigma auth login gdrive --client-id <id> --client-secret <s>` | Run the device code flow for Google Drive |
369+
| `enigma auth status` | Show which providers have stored tokens, expiry, scope |
370+
| `enigma auth logout <provider>` | Delete stored tokens for a provider |
371+
372+
The encrypted token store lives at `<config_dir>/oauth_tokens.enc`. It is unlocked with your Enigma passphrase — same one as the keystore.
373+
374+
293375

294376
### Environment Variables
295377

crates/enigma-cli/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,15 @@ glob = "0.3"
2727
rpassword = "5"
2828
hex.workspace = true
2929
futures.workspace = true
30+
webbrowser = { workspace = true, optional = true }
3031

3132
# OpenSSL (vendored for cross-compilation)
3233
openssl = { workspace = true, optional = true }
3334

3435
[features]
35-
default = []
36+
default = ["browser-open"]
37+
# Try to open the user's default browser during `enigma auth login` device flow.
38+
browser-open = ["dep:webbrowser"]
3639
vendored-openssl = ["dep:openssl"]
3740
azure-keyvault = ["enigma-keys/azure-keyvault"]
3841
gcp-secretmanager = ["enigma-keys/gcp-secretmanager"]

0 commit comments

Comments
 (0)