-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat(core): Encrypt secret custom fields and config args at rest #5051
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 14 commits
Commits
Show all changes
44 commits
Select commit
Hold shift + click to select a range
2bac314
feat(core): Add encryption and secret-access strategies
michaelbromley 35b4816
feat(core): Add `secret` flag to encrypt custom fields and config arg…
michaelbromley 74896f2
feat(core): Gate API access to decrypted secret field and config arg …
michaelbromley e25415f
test(core): Add e2e coverage for secret custom fields and config args
michaelbromley e126543
docs: Add secret fields guide
michaelbromley 36f7ae7
docs(core): Correct secret fields @since tags to 3.8.0
michaelbromley 6b3ab7f
fix(core): Gate secret config args centrally for all operation types
michaelbromley 23cf103
feat(core): Detect encryption key mismatch at bootstrap
michaelbromley 66568d6
fix(core): Require encryption secret via config, not process.env
michaelbromley 8b4ce03
fix(core): Restrict encryption key-check to SuperAdmin
michaelbromley e2626c8
fix(core): Redact secret config args by definition, not ciphertext
michaelbromley d407ded
fix(core): Derive encryption key with scrypt and warn on weak secret
michaelbromley 48d9cba
docs: update timestamps in auto-generated documentation files
vendure-ci-automation-bot[bot] 2fe1d9d
docs: Regenerate manifest.json
vendure-ci-automation-bot[bot] 0a6c2b1
fix(core): Preserve duplicate operation secrets by position, not code
michaelbromley e34b3d0
fix(core): Pass the owning entity to SecretAccessStrategy for custom …
michaelbromley 20d17fa
fix(core): Reject secret:true on unsupported custom field types
michaelbromley 54dea2d
fix(core): Only pass ciphertext to EncryptionStrategy.decrypt()
michaelbromley 23cb84b
test(core): Make secret custom field entity assertion self-contained
michaelbromley 66a2b69
fix(core): Preserve secret custom fields on all write paths
michaelbromley 4d6c1fa
fix(core): Skip secret machinery for preview and test operations
michaelbromley 6ce4cb9
fix(core): Correctness, robustness and cleanup from secret-field review
michaelbromley 246f330
test(core): Expand secret field coverage from review
michaelbromley 5f5bf89
docs: update SecretAccessStrategy documentation with line number fix …
vendure-ci-automation-bot[bot] a1263c9
docs: Regenerate manifest.json
vendure-ci-automation-bot[bot] c478ae3
refactor(core): Discover secret custom-field inputs from the schema
michaelbromley d530ef9
refactor(core): Single source of truth for configurable-operation reg…
michaelbromley fdec628
refactor(core): Share the CustomFieldsInput type-name suffix
michaelbromley 8d3690c
docs: update source line numbers in custom field documentation files
vendure-ci-automation-bot[bot] be0841b
docs: Regenerate manifest.json
vendure-ci-automation-bot[bot] 1cc06cb
fix(core): Namespace the encryption key-check settings store key unde…
michaelbromley 409c602
feat(dashboard): Default secret fields to a masked password input
michaelbromley ce3d70e
fix(dashboard): Query secret flag for custom fields and config args
michaelbromley 3c5a987
refactor(dashboard): Share secret input-component resolution
michaelbromley fb25eeb
fix(dashboard): Do not surface the secret redaction placeholder
michaelbromley 484ca4f
docs: Generate docs for source changes
vendure-ci-automation-bot[bot] e8ac5b2
docs: Regenerate manifest.json
vendure-ci-automation-bot[bot] 331e50f
docs: Tighten secret fields guide from review feedback
michaelbromley 4819ba3
chore(dashboard): Extract i18n message for hidden secret field label
michaelbromley c94c3a4
Merge remote-tracking branch 'origin/minor' into feat/secret-fields
michaelbromley cc5e87f
docs: Regenerate manifest.json
vendure-ci-automation-bot[bot] 1499461
docs: Generate docs for source changes
vendure-ci-automation-bot[bot] 0afbc8f
docs: Regenerate manifest.json
vendure-ci-automation-bot[bot] b746f78
fix(dashboard): Set secret flag when mapping struct fields to field c…
michaelbromley File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
127 changes: 127 additions & 0 deletions
127
docs/docs/guides/developer-guide/secret-fields/index.mdx
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| --- | ||
| title: "Secret fields" | ||
| metaTitle: "Encrypting secret custom fields and config args in Vendure" | ||
| metaDescription: "Store sensitive values such as API keys encrypted at rest and control who can read them via the API using the `secret` flag." | ||
| --- | ||
|
|
||
| Some data stored by Vendure is sensitive — payment-gateway API keys, webhook signing secrets and | ||
| similar. The `secret` flag lets you mark a custom field or a config arg so that its value is | ||
| **encrypted in the database** and is **only returned in decrypted form via the API to users who are | ||
| permitted to see it**. | ||
|
|
||
| ## Enabling encryption | ||
|
|
||
| Encryption requires a stable secret key. Configure an [`EncryptionStrategy`](/reference/typescript-api/configuration/encryption-strategy/) | ||
| in your VendureConfig and pass it the secret. The strategy does not read the environment itself — | ||
| following Vendure's convention, you read the environment variable in your config and pass the value | ||
| in explicitly: | ||
|
|
||
| ```ts | ||
| import { DefaultEncryptionStrategy, VendureConfig } from '@vendure/core'; | ||
|
|
||
| export const config: VendureConfig = { | ||
| // ... | ||
| systemOptions: { | ||
| encryptionStrategy: new DefaultEncryptionStrategy({ | ||
| secret: process.env.VENDURE_ENCRYPTION_KEY, | ||
| }), | ||
| }, | ||
| }; | ||
| ``` | ||
|
|
||
| ```sh | ||
| VENDURE_ENCRYPTION_KEY=your-long-random-secret | ||
| ``` | ||
|
|
||
| :::warning | ||
| The key must remain the same across restarts and deployments. If it changes, existing encrypted | ||
| data can no longer be decrypted. Do not commit it to source control, and back it up securely. | ||
| ::: | ||
|
|
||
| If a `secret` field or arg is configured but no encryption key is available, the server refuses to | ||
| start, with a message showing exactly which configuration to add. | ||
|
|
||
| ## Marking a custom field as secret | ||
|
|
||
| Set `secret: true` on a `string` or `text` custom field: | ||
|
|
||
| ```ts | ||
| customFields: { | ||
| Customer: [ | ||
| { name: 'externalApiKey', type: 'string', secret: true }, | ||
| ], | ||
| }, | ||
| ``` | ||
|
|
||
| The value is encrypted when saved and decrypted when loaded. `secret` cannot be combined with | ||
| `unique` or an explicit `length`, and is not supported on `relation`, `struct` or list fields. | ||
|
|
||
| ## Marking a config arg as secret | ||
|
|
||
| Set `secret: true` on a `string` config arg — for example the API key of a payment method handler: | ||
|
|
||
| ```ts | ||
| args: { | ||
| apiKey: { type: 'string', secret: true }, | ||
| }, | ||
| ``` | ||
|
|
||
| This works for any configurable operation — payment method handlers and eligibility checkers, | ||
| shipping calculators and checkers, promotion conditions and actions, collection filters, and any | ||
| custom `ConfigurableOperationDef` you define yourself. The encryption, redaction and | ||
| preservation-on-edit behaviour is handled centrally, so a custom operation does not need to | ||
| implement any of it. `secret` is only supported on non-list `string` args. | ||
|
michaelbromley marked this conversation as resolved.
Outdated
|
||
|
|
||
| ## Who can read the value | ||
|
|
||
| Reading the decrypted value requires the `Permission.ReadSecret` permission, which by default is | ||
| held only by the SuperAdmin. A user without it receives a redaction placeholder instead of the real | ||
| value. This behaviour is defined by the [`SecretAccessStrategy`](/reference/typescript-api/configuration/secret-access-strategy/), | ||
| which you can replace to apply your own logic (for example, revealing a secret only to certain | ||
| roles). Its input is a discriminated union on `kind`: for a `customField` it includes the entity | ||
| instance, its type and the field name; for a `configArg` it includes the arg name and, where the | ||
| GraphQL query path allows it to be determined, the owning entity type and field. | ||
|
|
||
| Writing a secret is **not** gated by `ReadSecret` — any user who is allowed to create or update the | ||
| entity may set its secret fields. | ||
|
|
||
| ## Editing without re-entering the secret | ||
|
|
||
| Because the API never returns the real value to an unauthorised user, the placeholder is what their | ||
| edit form has to submit back. When the placeholder is submitted on an update, the stored value is | ||
| left unchanged. To change the value, submit a new one; to clear it, submit an empty value. On a | ||
| _create_, the placeholder is rejected, since there is no existing value to preserve. | ||
|
|
||
| ## Moving data between environments | ||
|
|
||
| Secret values are stored encrypted, so a database dump contains ciphertext, not the original values. | ||
| The key that encrypted them lives only in your environment configuration, never in the database. This | ||
| means a dump can only be read in an environment that uses the **same** encryption key. | ||
|
|
||
| The first time an encryption key is used against a database, Vendure stores a small "key check" value | ||
| (a known string encrypted with that key). On every startup it verifies the configured key against | ||
| this check, so if you restore a dump into an environment with a different key — or change the key of | ||
| an existing database — the server **fails to start** with a clear error, rather than failing later | ||
| with scattered decryption errors when a secret is first read. | ||
|
|
||
| As a result: | ||
|
|
||
| - **To move data you want to keep usable** (for example promoting a staging database to production), | ||
| use the **same `VENDURE_ENCRYPTION_KEY`** in both environments. | ||
| - **To copy data into an environment that uses a different key** (for example pulling production data | ||
| onto a developer machine without sharing the production key), first clear the secret values in the | ||
| dump: set the encrypted custom-field columns to `NULL` and remove the secret config-arg values. | ||
| Otherwise the restored data cannot be decrypted with the local key. | ||
|
|
||
| Key rotation (re-encrypting existing data under a new key) is not currently supported. If you have | ||
| intentionally changed the key and there is no encrypted data to preserve, delete the | ||
| `encryption.keyCheck` entry from the settings store to re-bind the database to the new key. | ||
|
|
||
| ## Existing data and limitations | ||
|
|
||
| - Enabling `secret` on a field that already holds plain-text values does not break anything, but | ||
| those values are only encrypted the next time each record is saved. | ||
| - Decrypted values are available to all internal code (events, jobs, plugins). The redaction applies | ||
| only to the GraphQL API, so take care not to re-leak secrets in logs or exports. | ||
| - `secret` fields cannot be filtered or sorted, because the stored value is ciphertext. | ||
| - Key rotation is not supported in this version; changing the key makes existing data unreadable. | ||
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
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
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
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
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
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
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
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
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
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
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
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
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.