Skip to content

Commit afea70d

Browse files
authored
Merge pull request #51 from encryption4all/docs/api-key-validation
docs: cryptify api-key tier validates against pkg, not local allowlist
2 parents 15892be + 3d7fd57 commit afea70d

4 files changed

Lines changed: 43 additions & 8 deletions

File tree

docs/guide/architecture.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,12 @@ Both encryption and decryption support streaming (`ReadableStream`/`WritableStre
179179
| `GET` | `/v2/irma/key/{timestamp}` | Retrieve a User Secret Key (USK). Requires `Authorization: Bearer <jwt>`. The timestamp must match the one embedded in the ciphertext. |
180180
| `POST` | `/v2/irma/sign/key` | Retrieve signing keys. Authenticate with either an API key (`Bearer PG-...`) or a Yivi JWT. |
181181

182+
#### API Key Validation
183+
184+
| Method | Endpoint | Description |
185+
|---|---|---|
186+
| `GET` | `/v2/api-key/validate` | Confirm a `PG-…` API key is valid and return `{ tenant_id, organisation_name }`. Requires `Authorization: Bearer PG-…`. Used by sibling services (e.g. Cryptify) that need to gate per-tenant behaviour on a validated key without going through signing-key issuance. Validates against the shared `business_api_keys` table; only registered when the PKG is configured with a database pool. |
187+
182188
#### Health
183189

184190
| Method | Endpoint | Description |
@@ -194,11 +200,13 @@ The key issuance endpoints require a valid `Authorization: Bearer <jwt>` header.
194200

195201
| Method | Endpoint | Description |
196202
|---|---|---|
197-
| `POST` | `/fileupload/init` | Initialize a file upload. Returns a UUID and upload token. |
198-
| `PUT` | `/fileupload/{uuid}` | Upload a chunk. Uses `Content-Range` headers for offset tracking. Requires `cryptifytoken` header. |
203+
| `POST` | `/fileupload/init` | Initialize a file upload. Returns a UUID and upload token. Optional `Authorization: Bearer PG-…` unlocks the higher upload-quota tier; Cryptify forwards the bearer to PKG's `/v2/api-key/validate` and uses the returned tenant id for rolling-window accounting. |
204+
| `PUT` | `/fileupload/{uuid}` | Upload a chunk. Uses `Content-Range` headers for offset tracking. Requires `cryptifytoken` header. Carries the same `Authorization` bearer on each chunk as `init`. |
199205
| `POST` | `/fileupload/finalize/{uuid}` | Finalize the upload after all chunks are sent. |
200206
| `GET` | `/filedownload/{uuid}` | Download an encrypted file as a stream. |
201207

208+
See [Upload limits](/repos/cryptify#upload-limits) and [Authentication for the higher tier](/repos/cryptify#authentication-for-the-higher-tier) on the Cryptify page for the tier breakdown and PKG-unreachable behaviour (503 vs. 413).
209+
202210
## Component Diagram
203211

204212
```

docs/guide/concepts.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ The sender proves their email address via Yivi, just like the recipient does dur
194194
### API key signing (PostGuard for Business)
195195
For automated or server-side encryption, an API key replaces the Yivi step. The organization operating the sender's application is trusted to authenticate the sender through its own mechanisms.
196196

197+
The same API key also unlocks the higher upload-quota tier on Cryptify (100 GB/upload, 100 GB rolling) when the encrypted payload is delivered through Cryptify. The SDK attaches `Authorization: Bearer <apiKey>` to every upload request automatically; Cryptify forwards the bearer to PKG for validation. See [Upload limits](/repos/cryptify#upload-limits) on the Cryptify page.
198+
197199
When the recipient decrypts, the SDK returns a `sender` field with type `FriendlySender` containing the verified identity attributes of the sender. The Thunderbird addon extracts sender attributes to build identity badges:
198200

199201
```ts

docs/repos/cryptify.md

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@ The `chunk_size` setting caps the size of each `PUT /fileupload/{uuid}` body. Cl
3939

4040
Cryptify enforces three independent limits on every upload. They are constants in `src/store.rs`, not config options.
4141

42-
| Limit | Anonymous senders | API-key senders | Notes |
42+
| Limit | Default tier | API-key tier | Notes |
4343
|---|---|---|---|
44-
| Per-chunk size | `chunk_size` from config (default 5 MB) | same as anonymous | Bigger chunks are rejected with `400 Bad Request`. |
44+
| Per-chunk size | `chunk_size` from config (default 5 MB) | same as default | Bigger chunks are rejected with `400 Bad Request`. |
4545
| Per-upload size | 5 GB | 100 GB | Total bytes for a single upload session. |
46-
| Rolling window total | 5 GB per 14 days | 100 GB per 14 days | Sum of all uploads from the same sender email in the trailing 14 days. |
46+
| Rolling window total | 5 GB per 14 days | 100 GB per 14 days | Default tier accounts per sender email; API-key tier accounts per tenant id. |
4747

48-
A sender is identified by the email attribute disclosed in the encrypted envelope's signature. The rolling window only counts finalized uploads.
48+
The default tier identifies the sender by the email attribute disclosed in the encrypted envelope's signature. The API-key tier accounts on the validated tenant id (`api-key:<organizations.id>`) so a single tenant cannot evade quota by varying sender attributes. See [Authentication for the higher tier](#authentication-for-the-higher-tier) below.
49+
50+
The rolling window only counts finalized uploads.
4951

5052
When a request would push the sender over the per-upload or the rolling-window limit, the server responds with `413 Payload Too Large` and a JSON body:
5153

@@ -61,10 +63,31 @@ When a request would push the sender over the per-upload or the rolling-window l
6163

6264
`limit` is either `"per_upload"` or `"rolling_window"`. `resets_at` is an RFC 3339 timestamp for when the oldest counted upload expires from the rolling window. It is `null` for `per_upload` rejections, since the per-upload limit does not reset.
6365

64-
`GET /usage` returns the current state for the authenticated sender, including `used_bytes`, `limit_bytes`, `per_upload_limit_bytes`, `window_days`, and `resets_at`.
66+
`GET /usage` returns the current state for the authenticated sender, including `used_bytes`, `limit_bytes`, `per_upload_limit_bytes`, `window_days`, and `resets_at`. When the request includes a validated `Authorization: Bearer PG-…`, the response describes the per-tenant bucket (`api-key:<tenant>`); otherwise it describes the per-email bucket.
6567

6668
<small>[Source: src/store.rs#L11-L15](https://github.com/encryption4all/cryptify/blob/58883a86b369af08d92db93aa1025f9eba3c73eb/src/store.rs#L11-L15)</small>
6769

70+
## Authentication for the higher tier
71+
72+
Callers unlock the API-key tier by sending `Authorization: Bearer PG-…` on every upload request (`init`, each chunk PUT, and `finalize`). The key is a PostGuard for Business API key issued through the [postguard-business](/repos/postguard-business) portal.
73+
74+
Cryptify itself does not own the key allowlist. On `init` it forwards the bearer to PKG's `GET /v2/api-key/validate`, which authenticates against the shared `business_api_keys` table and returns the tenant id (`organizations.id`). Cryptify uses that id for tier selection and as the rolling-window accounting key. Validation runs only at `init` — once the upload session is established, the tier and accounting key are fixed for its lifetime.
75+
76+
| Validation outcome | Tier applied | Behaviour on cap exceeded |
77+
|---|---|---|
78+
| No `Authorization` header / non-PG bearer | Default | `413 Payload Too Large` |
79+
| PKG returns `2xx` with tenant id | API-key | `413 Payload Too Large` (at 100 GB) |
80+
| PKG returns `401`/`403` (unknown or expired key) | Default | `413 Payload Too Large` |
81+
| PKG unreachable for the full retry budget | Default + warning | **`503 Service Unavailable`** when the upload exceeds the default 5 GB cap; `413` otherwise behaviour matches default |
82+
83+
The PKG retry budget at `init` is 30 seconds with exponential backoff (250 ms → 5 s ceiling). Authoritative responses (`2xx` with body, `401`, `403`) short-circuit the retry loop. Connection errors and `5xx` are retried until the budget is exhausted.
84+
85+
The 503 response distinguishes "we couldn't tell whether you should have gotten the higher tier" from the regular 413 ("you're over your tier's cap"). Smaller uploads degrade silently to the default tier with a warning logged on the server, so transient PKG outages don't block uploads that would have fit anyway.
86+
87+
The legacy `X-Api-Key` header is no longer recognised; older clients that still send it are treated as default tier.
88+
89+
<small>[Source: src/main.rs](https://github.com/encryption4all/cryptify/blob/main/src/main.rs)</small>
90+
6891
## API
6992

7093
Cryptify exposes a file upload/download API. An OpenAPI 3.0 specification is available in `api-description.yaml` in the repository root. The main endpoints:

docs/repos/postguard-js.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,9 @@ const result = await opened.decrypt({
146146
## Signing Methods
147147

148148
```ts
149-
// Business API key (server-side)
149+
// Business API key (server-side). The SDK forwards this key to Cryptify
150+
// uploads as `Authorization: Bearer <apiKey>`, which unlocks the higher
151+
// upload-quota tier (see /repos/cryptify#upload-limits).
150152
pg.sign.apiKey('your-api-key')
151153

152154
// Yivi web session (browser, inline QR code)

0 commit comments

Comments
 (0)