Skip to content

Commit 5721e12

Browse files
committed
Add support for access token authentication
- Added ability to specify api_key or access_token at request time - Fixed bug with `yarn test [file]` not running just that file because it was combined with `tsd`. Made them two separate tasks. - Improved tests to catch errors when they don't instead of relying on mocha timeout to fail the test. - Updated CI
1 parent f8e80d6 commit 5721e12

File tree

11 files changed

+139
-26
lines changed

11 files changed

+139
-26
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-latest
1313
strategy:
1414
matrix:
15-
node-version: [18.x, 20.x]
15+
node-version: [18.x, 20.x, 22.x, 23.x, 24.x]
1616
fail-fast: false
1717
steps:
1818
- uses: actions/checkout@v4
@@ -29,6 +29,9 @@ jobs:
2929
- name: Run tests
3030
run: yarn test
3131

32+
- name: Run type definition tests
33+
run: yarn test:types
34+
3235
linters:
3336
name: Linters
3437
runs-on: ubuntu-latest

README.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,31 @@ yarn add emailable
2424

2525
## Usage
2626

27-
The library needs to be configured with your account's API key which is
28-
available in your [Emailable Dashboard](https://app.emailable.com/api). Require
29-
it with your API key:
27+
### Authentication
3028

31-
### Setup
29+
The Emailable API requires either an API key or an access token for
30+
authentication. API keys can be created and managed in the
31+
[Emailable Dashboard](https://app.emailable.com/api).
32+
33+
An API key can be set globally for the Emailable client:
3234

3335
```javascript
3436
// require with API key
35-
var emailable = require('emailable')('live_...')
37+
var emailable = require('emailable')('your_api_key')
3638

3739
// ES6 import
3840
import Emailable from 'emailable';
39-
const emailable = Emailable('live_...');
41+
const emailable = Emailable('your_api_key');
42+
```
43+
44+
Or, you can specify an `apiKey` or an `accessToken` with each request:
45+
46+
```javascript
47+
// set api_key at request time
48+
emailable.verify(apiKey: 'your_api_key')
49+
50+
// set access_token at request time
51+
emailable.verify(accessToken: 'your_api_key')
4052
```
4153

4254
### Verification

lib/client.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ const axios = require('axios')
44

55
class Client {
66

7-
constructor(key) {
7+
constructor(apiKey = null) {
88
this.instance = axios.create({ baseURL: 'https://api.emailable.com/v1/' })
9-
if (key) {
10-
this.instance.defaults.headers.common['Authorization'] = `Bearer ${key}`
11-
}
9+
this.apiKey = apiKey
1210
}
1311

1412
makeGetRequest(endpoint, params = {}) {
13+
this.setAuthentication(params)
14+
1515
return new Promise((resolve, reject) => {
1616
this.instance.get(endpoint, { params: params })
1717
.then(response => resolve(response.data))
@@ -29,6 +29,8 @@ class Client {
2929
}
3030

3131
makePostRequest(endpoint, data = {}) {
32+
this.setAuthentication(data)
33+
3234
return new Promise((resolve, reject) => {
3335
this.instance.post(endpoint, data)
3436
.then(response => resolve(response.data))
@@ -41,6 +43,13 @@ class Client {
4143
})
4244
}
4345

46+
setAuthentication(data) {
47+
const key = data.apiKey || data.accessToken || this.apiKey
48+
if (!key) return
49+
50+
this.instance.defaults.headers.common['Authorization'] = `Bearer ${key}`
51+
}
52+
4453
}
4554

4655
module.exports = Client

lib/emailable.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ declare class Emailable {
1919

2020
verify(email: string, options?: {}): Promise<any>
2121

22-
account(): Promise<any>
22+
account(options?: {}): Promise<any>
2323

2424
readonly client: Client
2525
readonly batches: Batches
2626
}
2727

28-
declare function _exports(apiKey: any): Emailable
28+
declare function _exports(apiKey?: any): Emailable
2929
export = _exports

lib/emailable.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const Batches = require('./batches')
55

66
class Emailable {
77

8-
constructor(apiKey) {
8+
constructor(apiKey = null) {
99
this.client = new Client(apiKey)
1010
this.batches = new Batches(this.client)
1111
}
@@ -14,8 +14,8 @@ class Emailable {
1414
return this.client.makePostRequest('verify', { email: email, ...options })
1515
}
1616

17-
account() {
18-
return this.client.makeGetRequest('account')
17+
account(options = {}) {
18+
return this.client.makeGetRequest('account', options)
1919
}
2020

2121
}

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"main": "lib/emailable.js",
66
"types": "lib/emailable.d.ts",
77
"scripts": {
8-
"test": "mocha --timeout 10000 && tsd",
8+
"test": "mocha --timeout 10000",
9+
"test:types": "tsd",
910
"lint": "eslint"
1011
},
1112
"tsd": {
@@ -40,5 +41,6 @@
4041
},
4142
"dependencies": {
4243
"axios": "^1.6.0"
43-
}
44+
},
45+
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
4446
}

test/account.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe('emailable.account()', () => {
1010
expect(response.owner_email).to.be.a('string')
1111
expect(response.available_credits).to.be.a('number')
1212
done()
13-
})
13+
}).catch(done)
1414
})
1515

1616
it('should return a 401 status code when no API key', done => {

test/authentication.spec.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
'use strict'
2+
3+
const expect = require('chai').expect
4+
const apiKey = 'test_7aff7fc0142c65f86a00'
5+
const email = 'jarrett@emailable.com'
6+
const emails = ['jarrett@emailable.com', 'support@emailable.com']
7+
8+
describe('authentication', () => {
9+
10+
it('should authenticate with a global api key configured', done => {
11+
const emailable = require('../lib/emailable')(apiKey)
12+
13+
Promise.all([
14+
emailable.verify(email).then(response => {
15+
expect(response.domain).to.be.a('string')
16+
}),
17+
18+
emailable.account().then(response => {
19+
expect(response.owner_email).to.be.a('string')
20+
}),
21+
22+
emailable.batches.verify(emails)
23+
.then(response => {
24+
expect(response.id).to.have.lengthOf(24)
25+
return emailable.batches.status(response.id)
26+
})
27+
.then(statusResponse => {
28+
expect(statusResponse.id).to.be.a('string')
29+
})
30+
])
31+
.then(() => done())
32+
.catch(done)
33+
})
34+
35+
it('should authenticate with an api key passed in at request time', done => {
36+
const emailable = require('../lib/emailable')()
37+
38+
Promise.all([
39+
emailable.verify(email, { apiKey: apiKey }).then(response => {
40+
expect(response.domain).to.be.a('string')
41+
}),
42+
43+
emailable.account({ apiKey: apiKey }).then(response => {
44+
expect(response.owner_email).to.be.a('string')
45+
}),
46+
47+
emailable.batches.verify(emails, { apiKey: apiKey })
48+
.then(response => {
49+
expect(response.id).to.have.lengthOf(24)
50+
return emailable.batches.status(response.id, { apiKey: apiKey })
51+
})
52+
.then(statusResponse => {
53+
expect(statusResponse.id).to.be.a('string')
54+
})
55+
])
56+
.then(() => done())
57+
.catch(done)
58+
})
59+
60+
it('should prioritize request time authentication over global', done => {
61+
const emailable = require('../lib/emailable')('invalid_api_key')
62+
63+
Promise.all([
64+
emailable.verify(email, { apiKey: apiKey }).then(response => {
65+
expect(response.domain).to.be.a('string')
66+
}),
67+
68+
emailable.account({ apiKey: apiKey }).then(response => {
69+
expect(response.owner_email).to.be.a('string')
70+
}),
71+
72+
emailable.batches.verify(emails, { apiKey: apiKey })
73+
.then(response => {
74+
expect(response.id).to.have.lengthOf(24)
75+
return emailable.batches.status(response.id, { apiKey: apiKey })
76+
})
77+
.then(statusResponse => {
78+
expect(statusResponse.id).to.be.a('string')
79+
})
80+
])
81+
.then(() => done())
82+
.catch(done)
83+
})
84+
85+
})

test/batches.spec.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe('emailable.batches.verify()', () => {
1010
emailable.batches.verify(emails).then(response => {
1111
expect(response.id).to.have.lengthOf(24)
1212
done()
13-
})
13+
}).catch(done)
1414
})
1515

1616
it('should return a payment error when passed { simulate: "payment_error" }', done => {
@@ -32,8 +32,8 @@ describe('emailable.batches.status()', () => {
3232
expect(response.reason_counts).to.be.a('object')
3333
expect(response.message).to.be.a('string')
3434
done()
35-
})
36-
})
35+
}).catch(done)
36+
}).catch(done)
3737
})
3838

3939
it('should return verifying response when passed { simulate: "verifying" }', done => {
@@ -43,8 +43,8 @@ describe('emailable.batches.status()', () => {
4343
expect(response.total).to.be.a('number')
4444
expect(response.message).to.be.a('string')
4545
done()
46-
})
47-
})
46+
}).catch(done)
47+
}).catch(done)
4848
})
4949

5050
})

test/emailable.test-d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const emailable = Emailable('test_xxxxxxxxxx')
66
expectType<Promise<any>>(emailable.verify('deliverable@example.com'))
77
expectType<Promise<any>>(emailable.verify('deliverable@example.com', { accept_all: true }))
88
expectType<Promise<any>>(emailable.account())
9+
expectType<Promise<any>>(emailable.account({ apiKey: 'test_xxxxxxxxxx' }))
910
expectType<Promise<any>>(emailable.batches.verify(['deliverable@example.com']))
1011
expectType<Promise<any>>(emailable.batches.verify(['deliverable@example.com'], { simulate: 'verifying' }))
1112
expectType<Promise<any>>(emailable.batches.status('xxxxxxxxxx'))

0 commit comments

Comments
 (0)