Skip to content

Commit 54e7c77

Browse files
feat(core): Encrypt secret custom fields and config args at rest (#5051)
1 parent 5d9947e commit 54e7c77

116 files changed

Lines changed: 3064 additions & 413 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
---
2+
title: "Secret fields"
3+
metaTitle: "Encrypting secret custom fields and config args in Vendure"
4+
metaDescription: "Store sensitive values such as API keys encrypted at rest and control who can read them via the API using the `secret` flag."
5+
---
6+
7+
Some data stored by Vendure is sensitive — payment-gateway API keys, webhook signing secrets and
8+
similar. The `secret` flag lets you mark a custom field or a config arg so that its value is
9+
**encrypted in the database** and is **only returned in decrypted form via the API to users who are
10+
permitted to see it**.
11+
12+
## Enabling encryption
13+
14+
Encryption requires a stable secret key. Configure an [`EncryptionStrategy`](/reference/typescript-api/configuration/encryption-strategy/)
15+
in your VendureConfig, passing the secret key to its constructor:
16+
17+
```ts
18+
import { DefaultEncryptionStrategy, VendureConfig } from '@vendure/core';
19+
20+
export const config: VendureConfig = {
21+
// ...
22+
systemOptions: {
23+
encryptionStrategy: new DefaultEncryptionStrategy({
24+
secret: process.env.VENDURE_ENCRYPTION_KEY,
25+
}),
26+
},
27+
};
28+
```
29+
30+
```sh
31+
VENDURE_ENCRYPTION_KEY=your-long-random-secret
32+
```
33+
34+
:::warning
35+
The key must remain the same across restarts and deployments. If it changes, existing encrypted
36+
data can no longer be decrypted. Do not commit it to source control, and back it up securely.
37+
:::
38+
39+
If a `secret` field or arg is configured but no encryption key is available, the server refuses to
40+
start, with a message showing exactly which configuration to add.
41+
42+
## Marking a custom field as secret
43+
44+
Set `secret: true` on a `string` or `text` custom field:
45+
46+
```ts
47+
customFields: {
48+
Customer: [
49+
{ name: 'externalApiKey', type: 'string', secret: true },
50+
],
51+
},
52+
```
53+
54+
The value is encrypted when saved and decrypted when loaded. `secret` cannot be combined with
55+
`unique` or an explicit `length`, and is not supported on `relation`, `struct` or list fields.
56+
57+
## Marking a config arg as secret
58+
59+
Set `secret: true` on a `string` config arg — for example the API key of a payment method handler:
60+
61+
```ts
62+
args: {
63+
apiKey: { type: 'string', secret: true },
64+
},
65+
```
66+
67+
This works for any configurable operation — payment method handlers and eligibility checkers,
68+
shipping calculators and checkers, promotion conditions and actions, collection filters, and any
69+
custom `ConfigurableOperationDef` you define yourself. The encryption, redaction and
70+
preservation-on-edit behaviour is handled centrally, so a custom operation does not need to
71+
implement any of it.
72+
73+
`secret` is only supported on non-list `string` args. Encryption turns the value into an opaque
74+
ciphertext string, so the arg has to be a `string`; list args are not supported because the
75+
redaction placeholder that preserves an unchanged secret on edit stands for a single value.
76+
77+
## Who can read the value
78+
79+
Reading the decrypted value requires the `Permission.ReadSecret` permission, which by default is
80+
held only by the SuperAdmin. A user without it receives a redaction placeholder instead of the real
81+
value. This behaviour is defined by the [`SecretAccessStrategy`](/reference/typescript-api/configuration/secret-access-strategy/),
82+
which you can replace to apply your own logic (for example, revealing a secret only to certain
83+
roles). Its input is a discriminated union on `kind`: for a `customField` it includes the entity
84+
instance, its type and the field name; for a `configArg` it includes the arg name and, where the
85+
GraphQL query path allows it to be determined, the owning entity type and field.
86+
87+
Writing a secret is **not** gated by `ReadSecret` — any user who is allowed to create or update the
88+
entity may set its secret fields.
89+
90+
## Editing without re-entering the secret
91+
92+
Because the API never returns the real value to an unauthorised user, the placeholder is what their
93+
edit form has to submit back. When the placeholder is submitted on an update, the stored value is
94+
left unchanged. To change the value, submit a new one; to clear it, submit an empty value. On a
95+
_create_, the placeholder is rejected, since there is no existing value to preserve.
96+
97+
## Moving data between environments
98+
99+
Secret values are stored encrypted, so a database dump contains ciphertext, not the original values.
100+
The key that encrypted them lives only in your environment configuration, never in the database. This
101+
means a dump can only be read in an environment that uses the **same** encryption key.
102+
103+
The first time an encryption key is used against a database, Vendure stores a small "key check" value
104+
(a known string encrypted with that key). On every startup it verifies the configured key against
105+
this check, so if you restore a dump into an environment with a different key — or change the key of
106+
an existing database — the server **fails to start** with a clear error, rather than failing later
107+
with scattered decryption errors when a secret is first read.
108+
109+
As a result:
110+
111+
- **To move data you want to keep usable** (for example promoting a staging database to production),
112+
use the **same `VENDURE_ENCRYPTION_KEY`** in both environments.
113+
- **To copy data into an environment that uses a different key** (for example pulling production data
114+
onto a developer machine without sharing the production key), first clear the secret values in the
115+
dump: set the encrypted custom-field columns to `NULL` and remove the secret config-arg values.
116+
Otherwise the restored data cannot be decrypted with the local key.
117+
118+
Key rotation (re-encrypting existing data under a new key) is not currently supported. If you have
119+
intentionally changed the key and there is no encrypted data to preserve, delete the
120+
`vendure.encryption.keyCheck` entry from the settings store to re-bind the database to the new key.
121+
122+
## Existing data and limitations
123+
124+
- Enabling `secret` on a field that already holds plain-text values does not break anything, but
125+
those values are only encrypted the next time each record is saved.
126+
- Decrypted values are available to all internal code (events, jobs, plugins). The redaction applies
127+
only to the GraphQL API, so take care not to re-leak secrets in logs or exports.
128+
- `secret` fields cannot be filtered or sorted, because the stored value is ciphertext.
129+
- Key rotation is not supported in this version; changing the key makes existing data unreadable.

docs/docs/reference/dashboard/form-components/password-input.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: "PasswordInput"
33
generated: true
44
---
5-
<GenerationInfo sourceFile="packages/dashboard/src/lib/components/data-input/password-form-input.tsx" sourceLine="12" packageName="@vendure/dashboard" />
5+
<GenerationInfo sourceFile="packages/dashboard/src/lib/components/data-input/password-form-input.tsx" sourceLine="14" packageName="@vendure/dashboard" />
66

77
A component for displaying a password input.
88

docs/docs/reference/typescript-api/assets/asset-options.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: "AssetOptions"
33
generated: true
44
---
5-
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="752" packageName="@vendure/core" />
5+
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="754" packageName="@vendure/core" />
66

77
The AssetOptions define how assets (images and other files) are named and stored, and how preview images are generated.
88

docs/docs/reference/typescript-api/auth/auth-options.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: "AuthOptions"
33
generated: true
44
---
5-
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="366" packageName="@vendure/core" />
5+
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="368" packageName="@vendure/core" />
66

77
The AuthOptions define how authentication and authorization is managed.
88

docs/docs/reference/typescript-api/auth/cookie-options.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: "CookieOptions"
33
generated: true
44
---
5-
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="261" packageName="@vendure/core" />
5+
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="263" packageName="@vendure/core" />
66

77
Options for the handling of the cookies used to track sessions (only applicable if
88
`authOptions.tokenMethod` is set to `'cookie'`). These options are passed directly

docs/docs/reference/typescript-api/auth/superadmin-credentials.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: "SuperadminCredentials"
33
generated: true
44
---
5-
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="928" packageName="@vendure/core" />
5+
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="930" packageName="@vendure/core" />
66

77
These credentials will be used to create the Superadmin user & administrator
88
when Vendure first bootstraps.

docs/docs/reference/typescript-api/common/bootstrap.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ generated: true
44
---
55
## bootstrap
66

7-
<GenerationInfo sourceFile="packages/core/src/bootstrap.ts" sourceLine="193" packageName="@vendure/core" />
7+
<GenerationInfo sourceFile="packages/core/src/bootstrap.ts" sourceLine="196" packageName="@vendure/core" />
88

99
Bootstraps the Vendure server. This is the entry point to the application.
1010

@@ -76,7 +76,7 @@ Parameters
7676

7777
## BootstrapOptions
7878

79-
<GenerationInfo sourceFile="packages/core/src/bootstrap.ts" sourceLine="48" packageName="@vendure/core" since="2.2.0" />
79+
<GenerationInfo sourceFile="packages/core/src/bootstrap.ts" sourceLine="51" packageName="@vendure/core" since="2.2.0" />
8080

8181
Additional options that can be used to configure the bootstrap process of the
8282
Vendure server.

docs/docs/reference/typescript-api/common/currency-code.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: "CurrencyCode"
33
generated: true
44
---
5-
<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="1136" packageName="@vendure/common" />
5+
<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="1138" packageName="@vendure/common" />
66

77
ISO 4217 currency code
88

docs/docs/reference/typescript-api/common/job-state.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: "JobState"
33
generated: true
44
---
5-
<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="2394" packageName="@vendure/common" />
5+
<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="2400" packageName="@vendure/common" />
66

77
The state of a Job in the JobQueue
88

docs/docs/reference/typescript-api/common/language-code.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: "LanguageCode"
33
generated: true
44
---
5-
<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="2412" packageName="@vendure/common" />
5+
<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="2418" packageName="@vendure/common" />
66

77
Languages in the form of a ISO 639-1 language code with optional
88
region or script modifier (e.g. de_AT). The selection available is based

0 commit comments

Comments
 (0)