Skip to content

Commit 2e215be

Browse files
authored
feat: 🎸 Added events for instrumentation and APM (#32)
Created a global event emitter for the client that sends instrumentation events
1 parent e702a50 commit 2e215be

File tree

11 files changed

+314
-103
lines changed

11 files changed

+314
-103
lines changed

‎__tests__/client.test.js‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const { createMemoryStore } = require('../lib/memoryStore')
33
const axios = require('axios')
44
const { generateKey } = require('../lib/crypto')
55
const { JWT } = require('@panva/jose')
6+
const { CONNECT_TO_OPERATOR_START, CONNECT_TO_OPERATOR } = require('../lib/events')
67
jest.mock('axios')
78

89
describe('client', () => {
@@ -98,6 +99,20 @@ describe('client', () => {
9899
expect(listener).toHaveBeenCalledTimes(1)
99100
})
100101

102+
it('calls event.emit with CONNECT_TO_OPERATOR_START', async () => {
103+
const listener = jest.fn()
104+
client.events.on(CONNECT_TO_OPERATOR_START, listener)
105+
await client.connect()
106+
expect(listener).toHaveBeenCalledWith({ retry: 0 })
107+
})
108+
109+
it('calls event.emit with CONNECT_TO_OPERATOR', async () => {
110+
const listener = jest.fn()
111+
client.events.on(CONNECT_TO_OPERATOR, listener)
112+
await client.connect()
113+
expect(listener).toHaveBeenCalled()
114+
})
115+
101116
it('signs the payload', async () => {
102117
await client.connect()
103118
const jwt = axios.post.mock.calls[0][1]

‎__tests__/data.test.js‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,11 @@ describe('data', () => {
9595
describe('#read', () => {
9696
let data
9797
function createJWE (data) {
98-
const signed = JWS.sign(JSON.stringify(data), JWK.importKey(signingKey), { kid: signingKey.kid })
98+
const signed = JWS.sign(JSON.stringify(data), JWK.asKey(signingKey), { kid: signingKey.kid })
9999
const encryptor = new JWE.Encrypt(signed)
100-
encryptor.recipient(JWK.importKey(toPublicKey(accountEncryptionKey)),
100+
encryptor.recipient(JWK.asKey(toPublicKey(accountEncryptionKey)),
101101
{ kid: accountEncryptionKey.kid })
102-
encryptor.recipient(JWK.importKey(toPublicKey(serviceEncryptionKey)),
102+
encryptor.recipient(JWK.asKey(toPublicKey(serviceEncryptionKey)),
103103
{ kid: serviceEncryptionKey.kid })
104104
return encryptor.encrypt('general')
105105
}
@@ -237,11 +237,11 @@ describe('data', () => {
237237
describe('#read', () => {
238238
let data
239239
function createJWE (data) {
240-
const signed = JWS.sign(JSON.stringify(data), JWK.importKey(signingKey), { kid: signingKey.kid })
240+
const signed = JWS.sign(JSON.stringify(data), JWK.asKey(signingKey), { kid: signingKey.kid })
241241
const encryptor = new JWE.Encrypt(signed)
242-
encryptor.recipient(JWK.importKey(toPublicKey(accountEncryptionKey)),
242+
encryptor.recipient(JWK.asKey(toPublicKey(accountEncryptionKey)),
243243
{ kid: accountEncryptionKey.kid })
244-
encryptor.recipient(JWK.importKey(toPublicKey(serviceEncryptionKey)),
244+
encryptor.recipient(JWK.asKey(toPublicKey(serviceEncryptionKey)),
245245
{ kid: serviceEncryptionKey.kid })
246246
return encryptor.encrypt('general')
247247
}

‎__tests__/keyProvider.test.js‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ const jsonToBase64 = (obj) => Buffer.from(JSON.stringify(obj), 'utf8').toString(
66
const base64ToJson = (str) => JSON.parse(Buffer.from(str, 'base64').toString('utf8'))
77

88
describe('KeyProvider', () => {
9-
let keyOptions, keyProvider, pemKey, clientKey, keyValueStore, domain, jwksURI
9+
let keyOptions, pemKey, clientKey, domain, jwksURI
10+
let keyProvider, keyValueStore
1011
beforeEach(async () => {
1112
keyValueStore = {
1213
load: jest.fn().mockName('load').mockResolvedValue(''),
@@ -25,13 +26,17 @@ describe('KeyProvider', () => {
2526
privateKeyEncoding: { type: 'pkcs1', format: 'pem' }
2627
}).privateKey
2728
clientKey = importPEM(pemKey, jwksURI, { use: 'sig', kid: `${jwksURI}/client_key` })
28-
keyProvider = new KeyProvider({ clientKey: pemKey, keyValueStore, keyOptions, jwksURI })
29+
keyProvider = new KeyProvider({
30+
config: { clientKey: pemKey, keyValueStore, keyOptions, jwksURI }
31+
})
2932
})
3033
it('works with a PEM client key', () => {
3134
expect(keyProvider.clientKey).toEqual(clientKey)
3235
})
3336
it('works with a JWK client key', () => {
34-
keyProvider = new KeyProvider({ clientKey, keyValueStore, keyOptions, jwksURI })
37+
keyProvider = new KeyProvider({
38+
config: { clientKey, keyValueStore, keyOptions, jwksURI }
39+
})
3540

3641
expect(keyProvider.clientKey).toEqual(clientKey)
3742
})

‎__tests__/tokens.test.js‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,10 @@ describe('tokens', () => {
207207
domain = 'https://mycv.work'
208208
area = 'edumacation'
209209
data = ['Jag älskar hästar']
210-
const signedData = await JWS.sign(JSON.stringify(data), JWK.importKey(serviceSigningKey), { kid: serviceSigningKey.kid })
210+
const signedData = await JWS.sign(JSON.stringify(data), JWK.asKey(serviceSigningKey), { kid: serviceSigningKey.kid })
211211
const encrypt = new JWE.Encrypt(signedData)
212-
encrypt.recipient(JWK.importKey(accountEncryptionKey), { kid: accountEncryptionKey.kid })
213-
encrypt.recipient(JWK.importKey(serviceEncryptionKey), { kid: serviceEncryptionKey.kid })
212+
encrypt.recipient(JWK.asKey(accountEncryptionKey), { kid: accountEncryptionKey.kid })
213+
encrypt.recipient(JWK.asKey(serviceEncryptionKey), { kid: serviceEncryptionKey.kid })
214214
jwe = encrypt.encrypt('general')
215215
})
216216
it('creates a valid jwt', async () => {

‎lib/client.js‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@ const axios = require('axios')
22
const routes = require('./routes')
33
const data = require('./data')
44
const KeyProvider = require('./keyProvider')
5-
const { EventEmitter } = require('events')
65
const { configSchema } = require('./schemas')
76
const { v4 } = require('uuid')
87
const { createAuthenticationUrl } = require('./auth')
98
const tokens = require('./tokens')
9+
const {
10+
emitter,
11+
CONNECT_TO_OPERATOR_START,
12+
CONNECT_TO_OPERATOR,
13+
CONNECT_TO_OPERATOR_ERROR
14+
} = require('./events')
1015

1116
const defaults = {
1217
jwksPath: '/jwks',
@@ -24,11 +29,11 @@ class Client {
2429
this.connecting = false
2530
this.config.jwksURI = `${config.clientId}${config.jwksPath}`
2631
this.config.eventsURI = `${config.clientId}${config.eventsPath}`
27-
this.keyProvider = new KeyProvider(this.config)
32+
this.events = emitter
33+
this.keyProvider = new KeyProvider(this)
2834
this.routes = routes(this)
2935
this.tokens = tokens(this)
3036
this.data = data(this)
31-
this.events = new EventEmitter()
3237
this.keyValueStore = config.keyValueStore
3338

3439
this.connect = this.connect.bind(this)
@@ -67,12 +72,15 @@ class Client {
6772

6873
const serviceRegistration = await this.tokens.createServiceRegistration()
6974
try {
75+
this.events.emit(CONNECT_TO_OPERATOR_START, { retry })
7076
this.events.emit('CONNECTING', retry)
7177
const result = await axios.post(`${this.config.operator}/api`, serviceRegistration, { headers: { 'content-type': 'application/jwt' } })
7278
this.connected = true
7379
this.connecting = false
80+
this.events.emit(CONNECT_TO_OPERATOR)
7481
this.events.emit('CONNECTED', result)
7582
} catch (err) {
83+
this.events.emit(CONNECT_TO_OPERATOR_ERROR, err)
7684
this.events.emit('CONNECTION ERROR', err)
7785
const timeout = Math.min(1000 * Math.pow(2, retry++), 5000)
7886
await new Promise((resolve) => setTimeout(resolve, timeout))

‎lib/crypto.js‎

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ const {
44
} = require('crypto')
55
const { promisify } = require('util')
66
const pem2jwk = require('pem-jwk').pem2jwk
7+
const {
8+
emitter,
9+
GENERATE_KEY_START,
10+
GENERATE_KEY,
11+
GENERATE_KEY_ERROR
12+
} = require('./events')
713

814
function toBase64Url (base64) {
915
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
@@ -17,23 +23,31 @@ function thumbprint ({ e, kty, n }) {
1723
}
1824

1925
async function generateKey (jwksURI, options = {}, modulusLength = 2048) {
20-
if (!jwksURI && !options.kid) {
21-
throw new Error('jwksURI must be passed in')
22-
}
23-
if (!options || !options.use) {
24-
throw new Error('{ use } must be passed as an option')
25-
}
26-
const { privateKey } = await promisify(generateKeyPair)('rsa', {
27-
modulusLength,
28-
publicKeyEncoding: { type: 'pkcs1', format: 'pem' },
29-
privateKeyEncoding: { type: 'pkcs1', format: 'pem' }
30-
})
31-
const key = pem2jwk(privateKey, options)
32-
if (!key.kid) {
33-
key.kid = `${jwksURI}/${await thumbprint(key)}`
34-
}
26+
emitter.emit(GENERATE_KEY_START, { modulusLength })
3527

36-
return key
28+
try {
29+
if (!jwksURI && !options.kid) {
30+
throw new Error('jwksURI must be passed in')
31+
}
32+
if (!options || !options.use) {
33+
throw new Error('{ use } must be passed as an option')
34+
}
35+
const { privateKey } = await promisify(generateKeyPair)('rsa', {
36+
modulusLength,
37+
publicKeyEncoding: { type: 'pkcs1', format: 'pem' },
38+
privateKeyEncoding: { type: 'pkcs1', format: 'pem' }
39+
})
40+
const key = pem2jwk(privateKey, options)
41+
if (!key.kid) {
42+
key.kid = `${jwksURI}/${await thumbprint(key)}`
43+
}
44+
emitter.emit(GENERATE_KEY, { modulusLength: modulusLength })
45+
46+
return key
47+
} catch (error) {
48+
emitter.emit(GENERATE_KEY_ERROR, error)
49+
throw error
50+
}
3751
}
3852

3953
function toPublicKey ({ e, kid, kty, n, use }) {

0 commit comments

Comments
 (0)