Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add base functionality #1

Merged
merged 13 commits into from
Oct 15, 2020
21 changes: 21 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"extends": ["standard", "prettier"],
"plugins": ["prettier"],
"env": {
"browser": false,
"commonjs": true,
"es2021": true
},
"parserOptions": {
"ecmaVersion": 12
},
"rules": {
"prettier/prettier": "error",
"import/order": [
"error",
{
"newlines-between": "always"
}
]
}
}
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 14.x]
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Restore cached dependencies
uses: actions/cache@v2
with:
path: node_modules
key: node-modules-${{ hashFiles('package.json') }}
- name: Install dependencies
run: npm install
- name: Lint
run: npm run lint
- name: Run Tests
run: npm run test:ci
8 changes: 8 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ We would like to avoid issues that require a follow up questions to identify the

## For Developers

All contributions should fit the linter, pass the tests and add new tests when new features are added.
You can test this by running

```
npm run lint
npm run test
```

---

<a id="developers-certificate-of-origin"></a>
Expand Down
89 changes: 89 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,95 @@

Simplify development of fastify-secrets plugins

> This module is intended for developers implemeting fastify-secrets plugins, not for developers using fastify-plugin in their fastify projects

## Installation

```
npm install --save fastify-secrets-core
```

## Usage

`fastify-secrets-core` simplifies development of `fastify-secrets` plugins and ensures that all plugins implements the same api.

Each plugin must only provide a simple client that interacts with the secrets storage and `fastify-secrets-core` will glue it to fastify.

### buildPlugin

Create a new fastify plugin by calling `buildPlugin` and providing a client class or constructor and options that will be forwarded to `fastify-plugin`.

`buildPlugin` will return a fastify plugin that can be exported.

#### Example

```js
const { buildPlugin } = require('fastify-secrets-core')
const Client = require('./client')

const plugin = buildPlugin(Client, {
name: 'fastify-secrets-example'
})
```

### Client

The client that needs to be provided to `fastify-secrets-core` should implement this methods:

- `constructor`
- `async get(ref)` (required) This will be called to fetch a single secret identified by reference. It's up to the implementation what ref is.
- `async close()` (optional) This method will be called, if present, when the plugin is done fetching the secrets to give a chance to implementations to close any connection or run any teardown code. No further calls to the `get` method will be run after close.

### Example

This is a basic client implementation that reads secrets from `process.env`

```js
class Client {
constructor() {
console.log('client contructor')
}

async get(ref) {
return process.env[ref]
}

async close() {
console.log('client close')
}
}

```

### Resulting plugin

The resulting plugin can be registered directly in fastify.
These are the available options:

- `secrets` (required). A non-empty object representing a map of secret keys and references. The plugin will decorate fastify with a `secrets` object with the same keys as this option but will replace the references with the actual values for the secret as fetched with `client.get(reference)`
- `namespace` (optional). If present, the plugin will add the secret values to `fastify.secrets.namespace` instead of `fastify.secrets`.
- `concurrency` (optional, defaults to 5). How many concurrent call will be made to `client.get`. This is handled by `fastify-secrets-core` and it's transparent to the implementation.

#### Example

Assuming a plugin is built as per the previous examples, it can be used as

```js
fastify.register(plugin, {
namespace: 'db',
concurrency: 5,
secrets: {
user: 'PG_USER',
pass: 'PG_PASS'
}
})

await fastify.ready()

console.log(fastify.secrets.db.pass)

```

## Contributing

See [CONTRIBUTING.md](./CONTRIBUTING.md)
Expand Down
58 changes: 58 additions & 0 deletions lib/build-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict'

const fp = require('fastify-plugin')
const pProps = require('p-props')

const DEFAULT_GET_CONCURRENCY = 5

function assertOptions(opts) {
if (!opts || !opts.secrets) {
throw new Error('fastify-secrets: no secrets requested')
}
}

function assertPluginAlreadyRegistered(fastify, opts) {
const namespace = opts.namespace

if (!namespace && fastify.secrets) {
throw new Error('fastify-secrets has already been registered')
}

if (namespace && fastify.secrets && fastify.secrets[namespace]) {
throw new Error(`fastify-secrets '${namespace}' instance name has already been registered`)
}
}

function buildPlugin(Client, pluginOpts) {
async function FastifySecretsPlugin(fastify, opts) {
assertOptions(opts)
assertPluginAlreadyRegistered(fastify, opts)

const client = new Client()
mcollina marked this conversation as resolved.
Show resolved Hide resolved
const concurrency = opts.concurrency || DEFAULT_GET_CONCURRENCY

const secrets = await pProps(opts.secrets, (value) => client.get(value), { concurrency })

const namespace = opts.namespace
if (namespace) {
if (!fastify.secrets) {
fastify.decorate('secrets', {})
}

fastify.secrets[namespace] = secrets
} else {
fastify.decorate('secrets', secrets)
}

if (client.close) {
await client.close()
}
}

return fp(FastifySecretsPlugin, {
fastify: '3.x',
...pluginOpts
})
}

module.exports = buildPlugin
7 changes: 7 additions & 0 deletions lib/fastify-secrets-core.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict'

const buildPlugin = require('./build-plugin')

module.exports = {
buildPlugin
}
Loading