Skip to content

Commit 8def0b7

Browse files
authored
feat: 🎸 No config needed for development (#42)
https://trello.com/c/Re9paxRB/316-remove-need-for-env-file
1 parent f164089 commit 8def0b7

File tree

20 files changed

+1718
-799
lines changed

20 files changed

+1718
-799
lines changed

‎.env‎

Lines changed: 0 additions & 16 deletions
This file was deleted.

‎Dockerfile‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ COPY ./package.json .
1010
COPY ./package-lock.json .
1111
RUN npm install --production
1212

13+
COPY ./scripts ./scripts
1314
COPY ./migrations ./migrations
1415
COPY ./lib ./lib
1516

‎README.md‎

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,47 @@
77

88
Backend for managing consents and data flow
99

10+
## What data is stored and where?
11+
12+
- Personal data is stored encrypted in the `PDS` (eg. Dropbox) specified by the user
13+
- Metadata (consents, permissions, id:s) and client data are stored in `Postgres` (this will probably change)
14+
- Temporary consent requests are stored in `Redis`
15+
- Requests and errors are logged in `elasticsearch/APM` for debugging purposes in test/dev
16+
1017
## Configuration
11-
Create a file named `.env` in the project directory, example for a developers machine:
18+
19+
The application runs with default cofiguration if `NODE_ENV` is `development`
20+
(default) or `test` (default when running tests). When running in `production`
21+
these environment variables *must* be set or the application will not start.
22+
23+
These variables may optionally be overridden in an `.env` file on your dev
24+
machine.
25+
1226
```
13-
HOST=http://localhost:3000
14-
# development, test or production
27+
# Run mode: development|test|production
1528
NODE_ENV=development
16-
# optional, defaults to 3000
29+
30+
# Host port, defaults to 3000
1731
PORT=3000
18-
# optional, apm will not be used if APM_SERVER is not set
19-
APM_SERVER=http://localhost:8200
20-
# optional, defaults to ''
21-
APM_TOKEN=abc
32+
# Full host address, defaults to http://[your ip]:[port]
33+
HOST=https://myservice.work
2234
23-
# For Postgres migrations
24-
DATABASE_URL=postgres://postgresuser:postgrespassword@localhost:5432/mydata
35+
# Postgres
36+
PGHOST='postgres-service'
37+
PGPORT='5432'
38+
PGUSER='some-username'
39+
PGPASSWORD='some-very-secret-password'
40+
PGDATABASE='egendata'
2541
42+
# Application performance management. turned off if val is ''
43+
APM_SERVER=http://apm-service:8200
44+
APM_TOKEN=mysecrettoken
2645
27-
# Client keys (generate your own or use these FOR TESTING ONLY)
28-
PUBLIC_KEY="-----BEGIN RSA PUBLIC KEY-----\nMIGJAoGBAOBHQz1EEVPboCx5o1jL2Dlhf5hCFuyQVMCnRoI7qH/zE8A7OR3uw74u\nqvHguOzyK5RO/slRvHz6aX7sgwpiOkXHh4VDWRwRb0gvnFIopwe3Y7fn1zLkpsET\nGqPgWvYmSYIT5dwwlrkYY6ek0oH3amYm186SNUFVDbzSf+Pyy7ILAgMBAAE=\n-----END RSA PUBLIC KEY-----\n"
46+
# Operator key (generate your own!!!)
2947
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQDgR0M9RBFT26AseaNYy9g5YX+YQhbskFTAp0aCO6h/8xPAOzkd\n7sO+Lqrx4Ljs8iuUTv7JUbx8+ml+7IMKYjpFx4eFQ1kcEW9IL5xSKKcHt2O359cy\n5KbBExqj4Fr2JkmCE+XcMJa5GGOnpNKB92pmJtfOkjVBVQ280n/j8suyCwIDAQAB\nAoGBAMwGqBl86ZJy0nSDN2EZF5ujoXJ+dOJBrogP5CmnYfL7y3Ttq1kakwFY7PPb\nLf+HkrN5ZXj5HVJIb14ihFcW4tBR2EtABhuv2J6ZNx0KnDxUj+mJlb7GNgr5eayI\nUibIu8/eQh2+CGMilI/KR8zlRiHpD8BgttfBaRktGIrzklQJAkEA9C8JgnAGUbPp\n3rc3dEZR6pEcOGI5Fjo3uvhbOYO5oa4tJszNF1Fh1oUmn17J6yoMnh0qPG4snL2B\nOgSB8OCOnwJBAOshovf7obbVZFzQ7ikYImT/pqz7f7eV1+Uv1MRfGsXAc0EAXDrh\nAPiJ5icWkeRDCFxaTAy/8lrDGgDcL2CSoRUCQQCem4L4x91C6rMJaEbL7vU8gL8s\n3JgqGOykNLfElwxXubQ4VKUO9Vywo9JfiIlth+WkOlt53zJ5KRqsXcstdB8PAkAo\nw6IfYA6/Reuqc8Z2dWqxG+lnoAqaZ24Qm+RFTz+y/RR+NnPG+W9Tp4SxTiZo7n4q\nlLUOmNCJj72YXJQSKBmpAkAyDc4PrJ3nFt45BOEnRuXE60Lv3VzLPdWggOLcKTbW\nr6NAWQS0VNdXEmJVmdoKFhJAeUvLrXPtBGqPS7HO6A8A\n-----END RSA PRIVATE KEY-----\n"
3048
```
31-
- `PORT` is the port for this service
32-
- `APM_SERVER` and `APM_TOKEN` is so that this service can reach [APM](https://www.npmjs.com/package/elastic-apm-node) for logging requests and errors
3349

34-
Good luck.
50+
## Logging
3551

36-
## What data is stored and where?
37-
- Personal data is stored encrypted in the `PDS` (eg. Dropbox) specified by the user
38-
- Metadata (consents, permissions, id:s) and client data are stored in `Postgres` (this will probably change)
39-
- Temporary consent requests are stored in `Redis`
40-
- Requests and errors are logged in `elasticsearch/APM` for debugging purposes in test/dev
52+
The Operator is instrumented to use [Elastic APM](https://www.elastic.co/products/apm)
53+
for performance and error logging.

‎__test__/adapters/pds.test.js‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
const pds = require(`../../lib/adapters/pds`)
2-
const dropbox = require(`../../lib/adapters/pds/dropbox`)
3-
jest.mock(`../../lib/adapters/pds/dropbox`)
1+
const pds = require('../../lib/adapters/pds')
2+
const dropbox = require('../../lib/adapters/pds/dropbox')
3+
jest.mock('../../lib/adapters/pds/dropbox')
44

55
describe('adapters/pds', () => {
66
describe('#get', () => {

‎__test__/data.test.js‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,13 @@ describe('data', () => {
5454
sub: '26eb214f-287b-4def-943c-55a6eefa2d91',
5555
aud: 'https://smoothoperator.com',
5656
iss: 'https://mycv.work',
57-
paths: [ {
58-
domain: 'https://mycv.work',
59-
area: 'favorite_cats',
60-
data: { txt: 'Some huge JWE' }
61-
} ],
57+
paths: [
58+
{
59+
domain: 'https://mycv.work',
60+
area: 'favorite_cats',
61+
data: { txt: 'Some huge JWE' }
62+
}
63+
],
6264
iat: 1562150432,
6365
exp: 1562154032
6466
}
@@ -129,10 +131,12 @@ describe('data', () => {
129131
sub: 'd82054d3-4115-49a0-ac5c-3325273d53b2',
130132
aud: 'https://smoothoperator.com',
131133
iss: 'https://mycv.work',
132-
paths: [ {
133-
domain: 'https://mycv.work',
134-
area: 'favorite_cats'
135-
} ],
134+
paths: [
135+
{
136+
domain: 'https://mycv.work',
137+
area: 'favorite_cats'
138+
}
139+
],
136140
iat: 1562323351,
137141
exp: 1562326951
138142
}

‎__test__/services/tokens.test.js‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,12 @@ describe('tokens', () => {
5656
sub: 'd82054d3-4115-49a0-ac5c-3325273d53b2',
5757
aud: 'https://smoothoperator.com',
5858
iss: 'https://mycv.work',
59-
paths: [ {
60-
domain,
61-
area
62-
} ],
59+
paths: [
60+
{
61+
domain,
62+
area
63+
}
64+
],
6365
iat: 1562323351,
6466
exp: 1562326951
6567
}

‎lib/adapters/apm.js‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1+
const config = require('../config')
2+
const { info } = require('../logger')
13
let apm
24

3-
if (process.env.APM_SERVER) {
5+
if (config.get('APM_SERVER')) {
46
apm = require('elastic-apm-node').start({
5-
serviceName: process.env.APP_NAME || 'egendata-operator', // Allowed characters: a-z, A-Z, 0-9, -, _, and space
6-
secretToken: process.env.APM_TOKEN || '', // Use if APM Server requires a token
7-
serverUrl: process.env.APM_SERVER, // Set APM Server URL
8-
captureBody: (process.env.NODE_ENV === 'production') // Don't save request body in production
7+
serviceName: config.get('APP_NAME'), // Allowed characters: a-z, A-Z, 0-9, -, _, and space
8+
secretToken: config.get('APM_TOKEN'), // Use if APM Server requires a token
9+
serverUrl: config.get('APM_SERVER'), // Set APM Server URL
10+
captureBody: (config.get('NODE_ENV') === 'production') // Don't save request body in production
911
? 'off'
1012
: 'errors'
1113
})
12-
console.log('APM instrumentation done')
14+
info('APM instrumentation done')
1315
} else {
14-
console.log('No APM instrumentation configured')
16+
info('No APM instrumentation configured')
1517
}
1618

1719
function setTransactionName (name) {

‎lib/adapters/postgres.js‎

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
const { Client } = require('pg')
2-
const config = {
3-
host: process.env.PGHOST || 'localhost',
4-
port: process.env.PGPORT || '5432',
5-
user: process.env.PGUSER || 'postgresuser',
6-
password: process.env.PGPASSWORD || 'postgrespassword',
7-
database: process.env.PGDATABASE || 'mydata'
2+
const config = require('../config')
3+
const pgconfig = {
4+
host: config.get('PGHOST'),
5+
port: config.get('PGPORT'),
6+
user: config.get('PGUSER'),
7+
password: config.get('PGPASSWORD'),
8+
database: config.get('PGDATABASE')
89
}
910

1011
const wait = (ms) => new Promise(resolve => setTimeout(() => resolve(), ms))
1112

1213
async function connect (attemptNo = 0) {
1314
try {
14-
const client = new Client(config)
15+
const client = new Client(pgconfig)
1516
await client.connect()
1617
return client
1718
} catch (err) {
@@ -49,7 +50,7 @@ async function transaction (queries) {
4950
await conn.query('BEGIN')
5051
const results = []
5152
for (const args of queries) {
52-
let result = await conn.query(...args)
53+
const result = await conn.query(...args)
5354
results.push(result)
5455
}
5556
await conn.query('COMMIT')
@@ -67,4 +68,4 @@ async function transaction (queries) {
6768
}
6869
}
6970

70-
module.exports = { query, multiple, transaction }
71+
module.exports = { connect, query, multiple, transaction }

‎lib/config.js‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
require('dotenv').config()
2+
const { JWK } = require('@panva/jose')
3+
const config = require('nconf')
4+
.argv()
5+
.env()
6+
.defaults({
7+
NODE_ENV: 'development',
8+
APP_NAME: 'egendata-operator'
9+
})
10+
11+
function exitWith (message) {
12+
process.stderr.write(`${message}\n`)
13+
process.exit(1)
14+
}
15+
16+
function privateKey (host, rsaKey) {
17+
const rxPEM = /^-----BEGIN RSA PRIVATE KEY-----\n([a-zA-Z0-9+/=]*\n)*-----END RSA PRIVATE KEY-----\n?$/
18+
if (typeof rsaKey !== 'string' || !rxPEM.test(rsaKey)) {
19+
exitWith('Unknown key format for PRIVATE_KEY')
20+
}
21+
22+
const keyConfig = {
23+
kid: `${host}/jwks/operator_key`,
24+
use: 'sig'
25+
}
26+
return JWK.asKey(rsaKey, keyConfig).toJWK(true)
27+
}
28+
29+
function publicKey ({ e, kid, kty, n, use }) {
30+
return { e, kid, kty, n, use }
31+
}
32+
33+
function checkMissingKeys (config, keys) {
34+
const missing = keys.filter(key => {
35+
return !config.get(key)
36+
})
37+
if (missing.length > 0) {
38+
exitWith(`Your config is missing the following keys: ${missing.join(', ')}`)
39+
}
40+
}
41+
42+
let ip, PORT, HOST, PGHOST, PGPORT, PGUSER, PGPASSWORD, PGDATABASE, PRIVATE_KEY, PUBLIC_KEY
43+
44+
switch (config.get('NODE_ENV')) {
45+
case 'development':
46+
ip = require('ip').address()
47+
PORT = config.get('PORT') || '3000'
48+
HOST = `http://${ip}:${PORT}`
49+
PGHOST = config.get('PGHOST') || ip
50+
PGPORT = config.get('PGPORT') || '5432'
51+
PGUSER = config.get('PGUSER') || 'postgresuser'
52+
PGPASSWORD = config.get('PGPASSWORD') || 'postgrespassword'
53+
PGDATABASE = config.get('PGDATABASE') || 'mydata'
54+
PRIVATE_KEY = privateKey(HOST, config.get('PRIVATE_KEY') || '-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQDgR0M9RBFT26AseaNYy9g5YX+YQhbskFTAp0aCO6h/8xPAOzkd\n7sO+Lqrx4Ljs8iuUTv7JUbx8+ml+7IMKYjpFx4eFQ1kcEW9IL5xSKKcHt2O359cy\n5KbBExqj4Fr2JkmCE+XcMJa5GGOnpNKB92pmJtfOkjVBVQ280n/j8suyCwIDAQAB\nAoGBAMwGqBl86ZJy0nSDN2EZF5ujoXJ+dOJBrogP5CmnYfL7y3Ttq1kakwFY7PPb\nLf+HkrN5ZXj5HVJIb14ihFcW4tBR2EtABhuv2J6ZNx0KnDxUj+mJlb7GNgr5eayI\nUibIu8/eQh2+CGMilI/KR8zlRiHpD8BgttfBaRktGIrzklQJAkEA9C8JgnAGUbPp\n3rc3dEZR6pEcOGI5Fjo3uvhbOYO5oa4tJszNF1Fh1oUmn17J6yoMnh0qPG4snL2B\nOgSB8OCOnwJBAOshovf7obbVZFzQ7ikYImT/pqz7f7eV1+Uv1MRfGsXAc0EAXDrh\nAPiJ5icWkeRDCFxaTAy/8lrDGgDcL2CSoRUCQQCem4L4x91C6rMJaEbL7vU8gL8s\n3JgqGOykNLfElwxXubQ4VKUO9Vywo9JfiIlth+WkOlt53zJ5KRqsXcstdB8PAkAo\nw6IfYA6/Reuqc8Z2dWqxG+lnoAqaZ24Qm+RFTz+y/RR+NnPG+W9Tp4SxTiZo7n4q\nlLUOmNCJj72YXJQSKBmpAkAyDc4PrJ3nFt45BOEnRuXE60Lv3VzLPdWggOLcKTbW\nr6NAWQS0VNdXEmJVmdoKFhJAeUvLrXPtBGqPS7HO6A8A\n-----END RSA PRIVATE KEY-----\n')
55+
PUBLIC_KEY = publicKey(PRIVATE_KEY)
56+
57+
module.exports = config.defaults({
58+
PORT,
59+
HOST,
60+
PGHOST,
61+
PGPORT,
62+
PGUSER,
63+
PGPASSWORD,
64+
PGDATABASE,
65+
PRIVATE_KEY,
66+
PUBLIC_KEY,
67+
APM_SERVER: `http://${ip}:8200`,
68+
APM_TOKEN: 'abc'
69+
})
70+
break
71+
case 'test':
72+
PORT = '3000'
73+
HOST = `http://localhost:${PORT}`
74+
PGHOST = 'localhost'
75+
PGPORT = '5432'
76+
PGUSER = 'postgresuser'
77+
PGPASSWORD = 'postgrespassword'
78+
PGDATABASE = 'mydata'
79+
PRIVATE_KEY = privateKey(HOST, '-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQDgR0M9RBFT26AseaNYy9g5YX+YQhbskFTAp0aCO6h/8xPAOzkd\n7sO+Lqrx4Ljs8iuUTv7JUbx8+ml+7IMKYjpFx4eFQ1kcEW9IL5xSKKcHt2O359cy\n5KbBExqj4Fr2JkmCE+XcMJa5GGOnpNKB92pmJtfOkjVBVQ280n/j8suyCwIDAQAB\nAoGBAMwGqBl86ZJy0nSDN2EZF5ujoXJ+dOJBrogP5CmnYfL7y3Ttq1kakwFY7PPb\nLf+HkrN5ZXj5HVJIb14ihFcW4tBR2EtABhuv2J6ZNx0KnDxUj+mJlb7GNgr5eayI\nUibIu8/eQh2+CGMilI/KR8zlRiHpD8BgttfBaRktGIrzklQJAkEA9C8JgnAGUbPp\n3rc3dEZR6pEcOGI5Fjo3uvhbOYO5oa4tJszNF1Fh1oUmn17J6yoMnh0qPG4snL2B\nOgSB8OCOnwJBAOshovf7obbVZFzQ7ikYImT/pqz7f7eV1+Uv1MRfGsXAc0EAXDrh\nAPiJ5icWkeRDCFxaTAy/8lrDGgDcL2CSoRUCQQCem4L4x91C6rMJaEbL7vU8gL8s\n3JgqGOykNLfElwxXubQ4VKUO9Vywo9JfiIlth+WkOlt53zJ5KRqsXcstdB8PAkAo\nw6IfYA6/Reuqc8Z2dWqxG+lnoAqaZ24Qm+RFTz+y/RR+NnPG+W9Tp4SxTiZo7n4q\nlLUOmNCJj72YXJQSKBmpAkAyDc4PrJ3nFt45BOEnRuXE60Lv3VzLPdWggOLcKTbW\nr6NAWQS0VNdXEmJVmdoKFhJAeUvLrXPtBGqPS7HO6A8A\n-----END RSA PRIVATE KEY-----\n')
80+
PUBLIC_KEY = publicKey(PRIVATE_KEY)
81+
82+
module.exports = config.defaults({
83+
PORT,
84+
HOST,
85+
PGHOST,
86+
PGPORT,
87+
PGUSER,
88+
PGPASSWORD,
89+
PGDATABASE,
90+
PRIVATE_KEY,
91+
PUBLIC_KEY,
92+
APM_SERVER: '',
93+
APM_TOKEN: ''
94+
})
95+
break
96+
default:
97+
checkMissingKeys(config, ['PORT', 'HOST', 'DATABASE_URL', 'PRIVATE_KEY'])
98+
99+
config.set('PRIVATE_KEY', privateKey(config.get('HOST'), '-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQDgR0M9RBFT26AseaNYy9g5YX+YQhbskFTAp0aCO6h/8xPAOzkd\n7sO+Lqrx4Ljs8iuUTv7JUbx8+ml+7IMKYjpFx4eFQ1kcEW9IL5xSKKcHt2O359cy\n5KbBExqj4Fr2JkmCE+XcMJa5GGOnpNKB92pmJtfOkjVBVQ280n/j8suyCwIDAQAB\nAoGBAMwGqBl86ZJy0nSDN2EZF5ujoXJ+dOJBrogP5CmnYfL7y3Ttq1kakwFY7PPb\nLf+HkrN5ZXj5HVJIb14ihFcW4tBR2EtABhuv2J6ZNx0KnDxUj+mJlb7GNgr5eayI\nUibIu8/eQh2+CGMilI/KR8zlRiHpD8BgttfBaRktGIrzklQJAkEA9C8JgnAGUbPp\n3rc3dEZR6pEcOGI5Fjo3uvhbOYO5oa4tJszNF1Fh1oUmn17J6yoMnh0qPG4snL2B\nOgSB8OCOnwJBAOshovf7obbVZFzQ7ikYImT/pqz7f7eV1+Uv1MRfGsXAc0EAXDrh\nAPiJ5icWkeRDCFxaTAy/8lrDGgDcL2CSoRUCQQCem4L4x91C6rMJaEbL7vU8gL8s\n3JgqGOykNLfElwxXubQ4VKUO9Vywo9JfiIlth+WkOlt53zJ5KRqsXcstdB8PAkAo\nw6IfYA6/Reuqc8Z2dWqxG+lnoAqaZ24Qm+RFTz+y/RR+NnPG+W9Tp4SxTiZo7n4q\nlLUOmNCJj72YXJQSKBmpAkAyDc4PrJ3nFt45BOEnRuXE60Lv3VzLPdWggOLcKTbW\nr6NAWQS0VNdXEmJVmdoKFhJAeUvLrXPtBGqPS7HO6A8A\n-----END RSA PRIVATE KEY-----\n'))
100+
config.set('PUBLIC_KEY', publicKey(config.get('PRIVATE_KEY')))
101+
102+
module.exports = config
103+
break
104+
}

‎lib/data.js‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ async function read ({ payload }, res, next) {
1414
}))
1515
const result = await multiple(statements)
1616
const reads = []
17-
for (let { rows } of result) {
18-
for (let row of rows) {
17+
for (const { rows } of result) {
18+
for (const row of rows) {
1919
const { pdsProvider, pdsCredentials, domain, area } = camelCase(row)
2020
const { readFile } = get({ pdsProvider, pdsCredentials })
2121

@@ -53,7 +53,7 @@ async function read ({ payload }, res, next) {
5353

5454
function toDataMap (paths) {
5555
const map = {}
56-
for (let { domain, area, data } of paths) {
56+
for (const { domain, area, data } of paths) {
5757
if (!map[domain]) {
5858
map[domain] = {}
5959
}
@@ -73,8 +73,8 @@ async function write ({ payload }, res, next) {
7373
const dataMap = toDataMap(payload.paths)
7474
const results = await multiple(statements)
7575
const writes = []
76-
for (let { rows } of results) {
77-
for (let row of rows) {
76+
for (const { rows } of results) {
77+
for (const row of rows) {
7878
const { pdsProvider, pdsCredentials, domain, area } = camelCase(row)
7979
const { outputFile } = get({ pdsProvider, pdsCredentials })
8080

0 commit comments

Comments
 (0)