fix: validate API key against pg-pkg, not a local allowlist#139
Merged
rubenhensen merged 1 commit intomainfrom May 6, 2026
Merged
fix: validate API key against pg-pkg, not a local allowlist#139rubenhensen merged 1 commit intomainfrom
rubenhensen merged 1 commit intomainfrom
Conversation
) The original `ApiKeyPresent` request guard only checked that an `X-Api-Key` header was present, admitting any non-empty value to the higher-quota tier (100 GB per upload, 100 GB rolling vs. the default 5 GB). Anyone could defeat the storage/bandwidth abuse controls with `X-Api-Key: anything`. Supersedes the closed PR #130, which validated against a hashed allowlist in cryptify's config — that approach drifted from the source of truth in postguard-business (`business_api_keys`) on every key rotation/revocation. Now: cryptify reads `Authorization: Bearer PG-…`, calls pg-pkg's new `GET /v2/api-key/validate`, and uses the returned tenant id (`organizations.id`) both for limit selection and as the rolling-window accounting key (`api-key:<tenant>`). The bespoke `X-Api-Key` header is gone — clients now use the same `Authorization: Bearer PG-…` shape pg-pkg already accepts. Failure modes per the rule "small uploads degrade silently, large uploads get an explicit error": - pg-pkg returns 401/403 → caller is degraded to default tier (their expired/fake key just doesn't earn the higher tier). - pg-pkg unreachable → retry up to 30s with exponential backoff. If still down, mark `api_key_validation_failed=true` on `FileState`. Chunks within the default 5 GB cap continue (with a warning logged); chunks exceeding it return 503 (cannot apply the higher tier without pg-pkg). - pg-pkg validates the key → tenant id stored on `FileState`, 100 GB tier applied, rolling window accounted per tenant. Tests: 7 new unit tests pin the bearer-extract rules (PG-prefix required, scheme `Bearer`/`bearer`, rejects missing/empty/wrong-scheme/non-PG tokens). Full suite: 37 pass. Depends on the pg-pkg `/v2/api-key/validate` endpoint being deployed first.
3 tasks
This was referenced May 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #123. Supersedes #130.
The
ApiKeyPresentrequest guard previously only checked that anX-Api-Keyheader was present — any value (X-Api-Key: x) admitted the caller to the higher-quota tier (100 GB/upload, 100 GB rolling vs. the default 5 GB). Closed PR #130 fixed this with a hashed allowlist inconf/config.toml, but that approach drifts from the source of truth in postguard-business (business_api_keys) every time a key is rotated or revoked.This PR routes the validation through pg-pkg's new
/v2/api-key/validate(encryption4all/postguard#167). Single source of truth, no drift.What changed
ApiKeyrequest guard now readsAuthorization: Bearer PG-…and calls pg-pkg's validate endpoint via asyncreqwest. Returned tenant id flows intoFileState.api_key_tenant: Option<String>.FileStatealso carriesapi_key_validation_failed: boolfor the case where pg-pkg was unreachable for the full retry budget at init time.Error::ServiceUnavailable(HTTP 503) for when an over-default upload runs intovalidation_failed=true— see failure modes below.api-key:<tenant>for API-tier callers, falling back to per-sender email otherwise. Same shape as the closed fix: validate X-Api-Key against configured allowlist #130, applied here too.conf/config.tomlis unchanged — no[[api_keys]]allowlist anymore.Failure modes
Per the rule "small uploads degrade silently, large uploads get an explicit error":
Retry policy: exponential backoff (250 ms → 5 s ceiling), 30 s total budget, only on connection errors and 5xx. 401/403 short-circuit immediately.
API contract change
Clients now send
Authorization: Bearer PG-…instead ofX-Api-Key: PG-…. Update callers before deploy (frontend, Outlook add-in, business portal API). Greppable on the oldX-Api-Keyheader name.Tests
7 new unit tests pin the bearer-extract rules:
Bearer PG-…andbearer PG-…(case-insensitive scheme)Basic, etc.)Full suite:
cargo test→ 37 passed; 0 failed.cargo fmt --checkclean.Deploy order
pg-pkg PR (encryption4all/postguard#167) ships first. If this lands before pg-pkg, every API-key upload would burn the 30 s retry budget, then 503 on over-default uploads / silently degrade on under-default ones.
Test plan
Authorizationheader → uploads up to 5 GB, 413 abovePG-…key → uploads up to 100 GB, 413 above; rolling window keys onapi-key:<tenant>PG-…key → 401 from pg-pkg, cryptify treats as default tier, 413 above 5 GB