Encryption Middleware
Field-level encryption is available through encryptionMiddleware, which ships
as a separate package so the core stays dependency-free:
npm install @tahanabavi/typefetch-encryptionMoved in v2.0.0. This used to be exported from
@tahanabavi/typefetch, which meant every consumer installedcrypto-jsandnode-forgewhether or not they encrypted anything.
It can:
Encrypt selected request body fields
Decrypt selected response fields
Process deeply nested objects
Process arrays
Use different encryption methods per field
Support custom encryption and decryption handlers
Supported methods:
type EncryptionMethod = "AES" | "DES" | "RSA" | "Base64" | "Custom";Registering the Middleware
import { encryptionMiddleware } from "@tahanabavi/typefetch-encryption";
client.use(encryptionMiddleware, {
keyProvider: async () => ({
type: "symmetric",
key: "my-secret-key",
}),
});Endpoint Encryption Config
const contracts = {
secure: {
createSecret: {
method: "POST",
path: "/secure",
request: z.object({
body: z.object({
secret: z.string(),
profile: z.object({
pin: z.string(),
}),
}),
}),
response: z.object({
id: z.string(),
token: z.string(),
}),
encryption: {
method: "AES",
request: {
secret: true,
profile: {
pin: "Base64",
},
},
response: {
token: true,
},
},
},
},
} as const;Usage:
await api.secure.createSecret({
body: {
secret: "private-value",
profile: {
pin: "1234",
},
},
});Before the request is sent:
secretis encrypted with AESprofile.pinis encoded with Base64
After the response is received:
tokenis decrypted with AES
Separate Request and Response Methods
encryption: {
method: {
request: "RSA",
response: "AES",
},
request: {
password: true,
},
response: {
token: true,
},
}Custom Encryption
client.use(encryptionMiddleware, {
keyProvider: async () => ({
type: "symmetric",
key: "custom-key",
}),
customHandlers: {
encrypt: async (value, key) => {
return `encrypted:${value}`;
},
decrypt: async (value, key) => {
return value.replace("encrypted:", "");
},
},
});Endpoint config:
encryption: {
method: "Custom",
request: {
secret: true,
},
response: {
token: true,
},
}Fail-Closed Behavior
By default, encryption should fail closed.
That means if request encryption fails, the request is not sent as plaintext.
client.use(encryptionMiddleware, {
keyProvider: async () => ({
type: "symmetric",
key: "secret",
}),
failClosed: true,
});For debugging, you may disable fail-closed behavior:
client.use(encryptionMiddleware, {
keyProvider: async () => ({
type: "symmetric",
key: "secret",
}),
failClosed: false,
});Use failClosed: false carefully.
Encryption Maps
Encryption maps describe which fields should be transformed.
encryption: {
method: "AES",
request: {
password: true,
profile: {
ssn: true,
},
metadata: {
publicValue: false,
},
},
}Map values:
| Value | Behavior |
|---|---|
true | Encrypt/decrypt using default method |
false | Skip field |
"AES" | Use AES for this field |
"DES" | Use DES for this field |
"RSA" | Use RSA for this field |
"Base64" | Use Base64 for this field |
"Custom" | Use custom handler |
{ ... } | Recursively process object |
[ ... ] | Process array items |
Array example:
encryption: {
method: "AES",
request: {
users: [
{
password: true,
},
],
},
}This applies the first array map to every item unless an index-specific map exists.