Skip to content

Commit 43c8ccb

Browse files
authored
Merge pull request #24 from emailable/add-access-token-auth
Add support for access token authentication
2 parents f8e80d6 + 5d2cde9 commit 43c8ccb

File tree

11 files changed

+155
-28
lines changed

11 files changed

+155
-28
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: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ 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+
const { apiKey, accessToken, ...filteredParams } = params
14+
const key = apiKey || accessToken || this.apiKey
15+
const headers = key ? { Authorization: `Bearer ${key}` } : {}
16+
1517
return new Promise((resolve, reject) => {
16-
this.instance.get(endpoint, { params: params })
18+
this.instance.get(endpoint, { params: filteredParams, headers: headers })
1719
.then(response => resolve(response.data))
1820
.catch(error => {
1921
if (error.response) {
@@ -29,8 +31,12 @@ class Client {
2931
}
3032

3133
makePostRequest(endpoint, data = {}) {
34+
const { apiKey, accessToken, ...filteredData } = data
35+
const key = apiKey || accessToken || this.apiKey
36+
const headers = key ? { Authorization: `Bearer ${key}` } : {}
37+
3238
return new Promise((resolve, reject) => {
33-
this.instance.post(endpoint, data)
39+
this.instance.post(endpoint, filteredData, { headers: headers })
3440
.then(response => resolve(response.data))
3541
.catch(error => {
3642
reject({

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": "[email protected]+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: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
'use strict'
2+
3+
const expect = require('chai').expect
4+
const apiKey = 'test_7aff7fc0142c65f86a00'
5+
const email = '[email protected]'
6+
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+
it('should not modify the original params object passed in', done => {
86+
const emailable = require('../lib/emailable')()
87+
const params = { apiKey: apiKey }
88+
89+
Promise.all([
90+
emailable.verify(email, params).then(() => {
91+
expect(params.apiKey).to.equal(apiKey)
92+
}),
93+
94+
emailable.account(params).then(() => {
95+
expect(params.apiKey).to.equal(apiKey)
96+
})
97+
])
98+
.then(() => done())
99+
.catch(done)
100+
})
101+
102+
})

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('[email protected]'))
77
expectType<Promise<any>>(emailable.verify('[email protected]', { 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(['[email protected]']))
1011
expectType<Promise<any>>(emailable.batches.verify(['[email protected]'], { simulate: 'verifying' }))
1112
expectType<Promise<any>>(emailable.batches.status('xxxxxxxxxx'))

0 commit comments

Comments
 (0)