@cipherstash/stashjs-typeorm v0.1.0
Stash.js TypeORM
This package is a TypeORM-specific package for using CipherStash in your app.
It allows you to add Queryable and Application-level Encryption (QuALE) to your TypeORM entities, adds extensions to perform encrypted queries and manages indexing and collecion creation.
Getting Started
To get started, add the package to your app:
npm install @cipherstash/stashjs-typeorm --save
You can use decorators to protect the fields in an Entity
with encryption.
The EncryptedColumn
decorator will encrypt the target field
before saving and decrypt when loading.
For example, to encrypt the firstName
and lastName
fields:
import { Entity, PrimaryGeneratedColumn } from "typeorm"
import { EncryptedColumn } from "@cipherstash/stashjs-typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@EncryptedColumn({ key })
firstName: string
@EncryptedColumn({ key })
lastName: string
}
The @EncryptedColumn
decorator wraps TypeORM's @Column
so you can pass the same options there.
For example:
@EncryptedColumn({ nullable: false })
email: string
Adding Query Capabilities to Encrypted Fields
Standard encryption is not searchable.
@EncryptedColumn
uses AES-256-GCM with a random IV to protect the data which is
excellent for security but makes normal queries impossible.
To address that, we are going to use the @Queryable()
decorator.
We also need to add a field to our entity called stashId
which
will store an ID linking each record to the CipherStash index.
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
import { EncryptedColumn, Queryable } from "@cipherstash/stashjs-typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Queryable()
@EncryptedColumn({ key })
firstName: string
@Queryable()
@EncryptedColumn({ key })
lastName: string
// Links records to CipherStash
@Column({ nullable: true })
stashId: string
}
@Queryable
annotates the entity to tell CipherStash which indexes to create.
When a field is saved it is broken down into a term vector based on the indexes.
These terms are encrypted using queryable encryption and indexed into a CipherStash collection.
Before we can query anything, we need to create a CipherStash collection and index our data.
We can do that via a TypeORM Repository
that has been extended for use with CipherStash.
You will need a CipherStash Workspace for this to work.
// The Data source for your app (see https://typeorm.io/data-source)
import { AppDataSource } from "./data-source"
// Your User Entity
import { User } from "./entity/User"
// Repo extension wrapper
import { wrapRepo } from "@cipherstash/stashjs-typeorm"
// Create a CipherStash collection for the User entity
const userRepo = wrapRepo(AppDataSource.getRepository(User))
await userRepo.createCollection()
To index all of your data you can use the reindex
function on the userRepo
(assuming you called wrapRepo
).
await userRepo.reindex()
Now we can query our data! We'll use the wrapped repo again.
# Get a CipherStash enabled query builder
const builder = userRepo.createCSQueryBuilder("user")
// Query by last name (exact)
await builder
.query(q => q.lastName.eq("Draper"))
.getMany()
// Partial match
await builder
.query(q => q.lastName_match.match("Drap"))
.getMany()
// Range Query
await builder
.query(q => q.dob.gte(new Date(1999, 1, 1)))
.getMany()
createCSQueryBuilder
returns an object that extends on TypeORM's standard query builder
so you can use it in the same way (and for basic things this will just work).
// Order by firstName
await builder
.order("user.firstName") // Uses an encrypted index for ordering
.getMany()
More complex queries can be achieved by combining calls to the query function and by chaining methods on the builder.
For example, to find all users with last name matching the partial query "smi", ordered by firstName, selecting only firstName and lastName, with limit and offset applied:
await builder
.query(q => q.lastName_match.match("smi"))
.order("user.firstName")
.select(["user.firstName", "user.lastName"])
.limit(10)
.offset(20)
.getMany()
Keeping Indexes in Sync
TypeORM can (and probably should!) be configured to keep indexes
in sync with your database.
That's achieved by using a subscriber called IndexingSubscriber
,
Add IndexingSubcriber
to the subscribers list in your data-source
and every time a record is created, updated, deleted or recovered it will
be indexed (or deindexed) into CipherStash.
import { IndexingSubscriber } from "@cipherstash/stashjs-typeorm"
export const AppDataSource = new DataSource({
type: "postgres",
entities: [User],
...
subscribers: [IndexingSubscriber],
})
Key Management
Encryption is only as secure as the key used. While there are many ways to keep keys secure, one good way is to store your encryption key in an environment variable.
// Entity
import { key } from "./config"
@Entity()
export class User {
@Queryable()
@ProtectedColumn({ key })
firstName: string
//...
}
And the config:
export const key = process.env["ENCRYPTION_KEY"]