-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tenant persistence with in-memory key-value storage (#144)
- Loading branch information
1 parent
f581e6b
commit 3ae44da
Showing
16 changed files
with
378 additions
and
8 deletions.
There are no files selected for viewing
This file contains 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 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
60 changes: 60 additions & 0 deletions
60
apps/policy-engine/src/app/persistence/repository/__test__/unit/tenant.repository.spec.ts
This file contains 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,60 @@ | ||
import { DataStoreConfiguration } from '@narval/policy-engine-shared' | ||
import { Test } from '@nestjs/testing' | ||
import { KeyValueRepository } from '../../../../../shared/module/key-value/core/repository/key-value.repository' | ||
import { KeyValueService } from '../../../../../shared/module/key-value/core/service/key-value.service' | ||
import { InMemoryKeyValueRepository } from '../../../../../shared/module/key-value/persistence/repository/in-memory-key-value.repository' | ||
import { Tenant } from '../../../../../shared/types/domain.type' | ||
import { TenantRepository } from '../../../repository/tenant.repository' | ||
|
||
describe(TenantRepository.name, () => { | ||
let repository: TenantRepository | ||
let inMemoryKeyValueRepository: InMemoryKeyValueRepository | ||
|
||
beforeEach(async () => { | ||
inMemoryKeyValueRepository = new InMemoryKeyValueRepository() | ||
|
||
const module = await Test.createTestingModule({ | ||
providers: [ | ||
KeyValueService, | ||
TenantRepository, | ||
{ | ||
provide: KeyValueRepository, | ||
useValue: inMemoryKeyValueRepository | ||
} | ||
] | ||
}).compile() | ||
|
||
repository = module.get<TenantRepository>(TenantRepository) | ||
}) | ||
|
||
describe('create', () => { | ||
const now = new Date() | ||
|
||
const dataStoreConfiguration: DataStoreConfiguration = { | ||
dataUrl: 'a-url-that-doesnt-need-to-exist-for-the-purpose-of-this-test', | ||
signatureUrl: 'a-url-that-doesnt-need-to-exist-for-the-purpose-of-this-test', | ||
keys: [] | ||
} | ||
|
||
const tenant: Tenant = { | ||
clientId: 'test-client-id', | ||
clientSecret: 'test-client-secret', | ||
dataStore: { | ||
entity: dataStoreConfiguration, | ||
policy: dataStoreConfiguration | ||
}, | ||
createdAt: now, | ||
updatedAt: now | ||
} | ||
|
||
it('creates a new tenant', async () => { | ||
await repository.create(tenant) | ||
|
||
const value = await inMemoryKeyValueRepository.get(repository.getKey(tenant.clientId)) | ||
const actualTenant = await repository.findByClientId(tenant.clientId) | ||
|
||
expect(value).not.toEqual(null) | ||
expect(tenant).toEqual(actualTenant) | ||
}) | ||
}) | ||
}) |
37 changes: 37 additions & 0 deletions
37
apps/policy-engine/src/app/persistence/repository/tenant.repository.ts
This file contains 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,37 @@ | ||
import { Injectable } from '@nestjs/common' | ||
import { KeyValueService } from '../../../shared/module/key-value/core/service/key-value.service' | ||
import { tenantSchema } from '../../../shared/schema/tenant.schema' | ||
import { Tenant } from '../../../shared/types/domain.type' | ||
|
||
@Injectable() | ||
export class TenantRepository { | ||
constructor(private keyValueService: KeyValueService) {} | ||
|
||
async findByClientId(clientId: string): Promise<Tenant | null> { | ||
const value = await this.keyValueService.get(this.getKey(clientId)) | ||
|
||
if (value) { | ||
return this.decode(value) | ||
} | ||
|
||
return null | ||
} | ||
|
||
async create(tenant: Tenant): Promise<Tenant> { | ||
await this.keyValueService.set(this.getKey(tenant.clientId), this.encode(tenant)) | ||
|
||
return tenant | ||
} | ||
|
||
getKey(clientId: string): string { | ||
return `${clientId}:tenant` | ||
} | ||
|
||
private encode(tenant: Tenant): string { | ||
return JSON.stringify(tenant) | ||
} | ||
|
||
private decode(value: string): Tenant { | ||
return tenantSchema.parse(JSON.parse(value)) | ||
} | ||
} |
File renamed without changes.
7 changes: 7 additions & 0 deletions
7
apps/policy-engine/src/shared/module/key-value/core/repository/key-value.repository.ts
This file contains 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,7 @@ | ||
export const KeyValueRepository = Symbol('KeyValueRepository') | ||
|
||
export interface KeyValueRepository { | ||
get(key: string): Promise<string | null> | ||
set(key: string, value: string): Promise<boolean> | ||
delete(key: string): Promise<boolean> | ||
} |
44 changes: 44 additions & 0 deletions
44
...y-engine/src/shared/module/key-value/core/service/__test__/unit/key-value.service.spec.ts
This file contains 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,44 @@ | ||
import { ConfigModule } from '@nestjs/config' | ||
import { Test } from '@nestjs/testing' | ||
import { load } from '../../../../../../../policy-engine.config' | ||
import { InMemoryKeyValueRepository } from '../../../../persistence/repository/in-memory-key-value.repository' | ||
import { KeyValueRepository } from '../../../repository/key-value.repository' | ||
import { KeyValueService } from '../../key-value.service' | ||
|
||
describe('foo', () => { | ||
let service: KeyValueService | ||
let inMemoryKeyValueRepository: InMemoryKeyValueRepository | ||
|
||
beforeEach(async () => { | ||
inMemoryKeyValueRepository = new InMemoryKeyValueRepository() | ||
|
||
const module = await Test.createTestingModule({ | ||
imports: [ | ||
ConfigModule.forRoot({ | ||
load: [load], | ||
isGlobal: true | ||
}) | ||
], | ||
providers: [ | ||
KeyValueService, | ||
{ | ||
provide: KeyValueRepository, | ||
useValue: inMemoryKeyValueRepository | ||
} | ||
] | ||
}).compile() | ||
|
||
service = module.get<KeyValueService>(KeyValueService) | ||
}) | ||
|
||
describe('set', () => { | ||
it('sets encrypted value in the key-value storage', async () => { | ||
const key = 'test-key' | ||
const value = 'not encrypted value' | ||
|
||
await service.set(key, value) | ||
|
||
expect(await service.get(key)).toEqual(value) | ||
}) | ||
}) | ||
}) |
30 changes: 30 additions & 0 deletions
30
apps/policy-engine/src/shared/module/key-value/core/service/key-value.service.ts
This file contains 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,30 @@ | ||
import { Inject, Injectable } from '@nestjs/common' | ||
import { KeyValueRepository } from '../repository/key-value.repository' | ||
|
||
/** | ||
* The key-value service is the main interface to interact with any storage | ||
* back-end. Since the storage backend lives outside the engine, it's considered | ||
* untrusted so the engine will encrypt the data before it sends them to the | ||
* storage. | ||
* | ||
* It's because of that the key-value service assumes data is always encrypted. | ||
* If you need non-encrypted data, you can use the key-value repository. | ||
*/ | ||
@Injectable() | ||
export class KeyValueService { | ||
constructor(@Inject(KeyValueRepository) private keyValueRepository: KeyValueRepository) {} | ||
|
||
async get(key: string): Promise<string | null> { | ||
// TODO (@wcalderipe, 01/03/2024): Add decryption step. | ||
return this.keyValueRepository.get(key) | ||
} | ||
|
||
async set(key: string, value: string): Promise<boolean> { | ||
// TODO (@wcalderipe, 01/03/2024): Add encryption step. | ||
return this.keyValueRepository.set(key, value) | ||
} | ||
|
||
async delete(key: string): Promise<boolean> { | ||
return this.keyValueRepository.delete(key) | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
apps/policy-engine/src/shared/module/key-value/key-value.module.ts
This file contains 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,16 @@ | ||
import { Module } from '@nestjs/common' | ||
import { KeyValueRepository } from './core/repository/key-value.repository' | ||
import { KeyValueService } from './core/service/key-value.service' | ||
import { InMemoryKeyValueRepository } from './persistence/repository/in-memory-key-value.repository' | ||
|
||
@Module({ | ||
providers: [ | ||
KeyValueService, | ||
{ | ||
provide: KeyValueRepository, | ||
useClass: InMemoryKeyValueRepository | ||
} | ||
], | ||
exports: [KeyValueService, KeyValueRepository] | ||
}) | ||
export class KeyValueModule {} |
23 changes: 23 additions & 0 deletions
23
...gine/src/shared/module/key-value/persistence/repository/in-memory-key-value.repository.ts
This file contains 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,23 @@ | ||
import { Injectable } from '@nestjs/common' | ||
import { KeyValueRepository } from '../../core/repository/key-value.repository' | ||
|
||
@Injectable() | ||
export class InMemoryKeyValueRepository implements KeyValueRepository { | ||
private store = new Map<string, string>() | ||
|
||
async get(key: string): Promise<string | null> { | ||
return this.store.get(key) || null | ||
} | ||
|
||
async set(key: string, value: string): Promise<boolean> { | ||
this.store.set(key, value) | ||
|
||
return true | ||
} | ||
|
||
async delete(key: string): Promise<boolean> { | ||
this.store.delete(key) | ||
|
||
return true | ||
} | ||
} |
This file contains 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,13 @@ | ||
import { dataStoreConfigurationSchema } from 'packages/policy-engine-shared/src/lib/schema/data-store.schema' | ||
import { z } from 'zod' | ||
|
||
export const tenantSchema = z.object({ | ||
clientId: z.string(), | ||
clientSecret: z.string(), | ||
dataStore: z.object({ | ||
entity: dataStoreConfigurationSchema, | ||
policy: dataStoreConfigurationSchema | ||
}), | ||
createdAt: z.coerce.date(), | ||
updatedAt: z.coerce.date() | ||
}) |
This file contains 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 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
37 changes: 37 additions & 0 deletions
37
packages/policy-engine-shared/src/lib/schema/data-store.schema.ts
This file contains 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,37 @@ | ||
import { z } from 'zod' | ||
import { entitiesSchema } from './entity.schema' | ||
|
||
export const jsonWebKeySetSchema = z.object({ | ||
kty: z.string().describe('Key Type (e.g. RSA or EC'), | ||
use: z.string(), | ||
kid: z.string().describe('Arbitrary key ID'), | ||
alg: z.string().describe('Algorithm'), | ||
n: z.string().describe('Key modulus'), | ||
e: z.string().describe('Key exponent') | ||
}) | ||
|
||
export const dataStoreProtocolSchema = z.enum(['file']) | ||
|
||
export const dataStoreConfigurationSchema = z.object({ | ||
dataUrl: z.string(), | ||
signatureUrl: z.string(), | ||
keys: z.array(jsonWebKeySetSchema) | ||
}) | ||
|
||
export const entityDataSchema = z.object({ | ||
entity: z.object({ | ||
data: entitiesSchema | ||
}) | ||
}) | ||
|
||
export const entitySignatureSchema = z.object({ | ||
entity: z.object({ | ||
signature: z.string() | ||
}) | ||
}) | ||
|
||
export const entityJsonWebKeySetSchema = z.object({ | ||
entity: z.object({ | ||
keys: z.array(jsonWebKeySetSchema) | ||
}) | ||
}) |
Oops, something went wrong.