Skip to content
TypeWire

@tahanabavi/typefetch

documents v1.10.0

The typed HTTP client every other package is built around.

Encryption Middleware

Field-level encryption is available through encryptionMiddleware, which ships as a separate package so the core stays dependency-free:

bash
npm install @tahanabavi/typefetch-encryption

Moved in v2.0.0. This used to be exported from @tahanabavi/typefetch, which meant every consumer installed crypto-js and node-forge whether 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:

ts
type EncryptionMethod = "AES" | "DES" | "RSA" | "Base64" | "Custom";

Registering the Middleware

ts
import { encryptionMiddleware } from "@tahanabavi/typefetch-encryption";

client.use(encryptionMiddleware, {
keyProvider: async () => ({
type: "symmetric",
key: "my-secret-key",
}),
});

Endpoint Encryption Config

ts
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:

ts
await api.secure.createSecret({
body: {
secret: "private-value",
profile: {
pin: "1234",
},
},
});

Before the request is sent:

  • secret is encrypted with AES

  • profile.pin is encoded with Base64

After the response is received:

  • token is decrypted with AES

Separate Request and Response Methods

ts
encryption: {
method: {
request: "RSA",
response: "AES",
},
request: {
password: true,
},
response: {
token: true,
},
}

Custom Encryption

ts
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:

ts
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.

ts
client.use(encryptionMiddleware, {
keyProvider: async () => ({
type: "symmetric",
key: "secret",
}),
failClosed: true,
});

For debugging, you may disable fail-closed behavior:

ts
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.

ts
encryption: {
method: "AES",
request: {
password: true,
profile: {
ssn: true,
},
metadata: {
publicValue: false,
},
},
}

Map values:

ValueBehavior
trueEncrypt/decrypt using default method
falseSkip 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:

ts
encryption: {
method: "AES",
request: {
users: [
{
password: true,
},
],
},
}

This applies the first array map to every item unless an index-specific map exists.