Skip to content

Commit 87f2b87

Browse files
committed
fix(AuthMiddleware): Fix catching 401 error, add tests
Closes #42
1 parent 6d8f5a2 commit 87f2b87

File tree

4 files changed

+169
-12
lines changed

4 files changed

+169
-12
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ Middlewares
6969
- `forceRetry` - function(cb, delay), when request is delayed for next retry, middleware will call this function and pass to it a callback and delay time. When you call this callback, middleware will proceed request immediately (default: `false`).
7070
- **authMiddleware** - for adding auth token, and refreshing it if gets 401 response from server.
7171
- `token` - string or function(req) which returns token. If function is provided, then it will be called for every request (so you may change tokens on fly).
72-
- `tokenRefreshPromise`: - function(req, err) which must return promise with new token, called only if server returns 401 status code and this function is provided.
72+
- `tokenRefreshPromise`: - function(req, err) which must return promise or regular value with a new token. This function is called when server returns 401 status code. After receiving a new token, middleware re-run query to the server with it seamlessly for Relay.
7373
- `allowEmptyToken` - allow made a request without Authorization header if token is empty (default: `false`).
7474
- `prefix` - prefix before token (default: `'Bearer '`).
7575
- `header` - name of the HTTP header to pass the token in (default: `'Authorization'`).

src/middleware/auth.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,26 @@ export default function authMiddleware(opts = {}) {
3838
}
3939
return next(req);
4040
})
41-
.then(res => {
42-
if (res.status === 401 && tokenRefreshPromise) {
43-
throw new WrongTokenError('Received status 401 from server', res);
41+
.catch(err => {
42+
if (
43+
err &&
44+
err.fetchResponse &&
45+
err.fetchResponse.status === 401 &&
46+
tokenRefreshPromise
47+
) {
48+
throw new WrongTokenError(
49+
'Received status 401 from server',
50+
err.fetchResponse
51+
);
52+
} else {
53+
throw err;
4454
}
45-
return res;
4655
})
4756
.catch(err => {
4857
if (err.name === 'WrongTokenError') {
4958
if (!tokenRefreshInProgress) {
50-
tokenRefreshInProgress = tokenRefreshPromise(
51-
req,
52-
err.res
59+
tokenRefreshInProgress = Promise.resolve(
60+
tokenRefreshPromise(req, err.res)
5361
).then(newToken => {
5462
tokenRefreshInProgress = null;
5563
return newToken;

test/middleware/auth.test.js

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { assert } from 'chai';
2+
import fetchMock from 'fetch-mock';
3+
import { RelayNetworkLayer } from '../../src';
4+
import { mockReq } from '../testutils';
5+
import authMiddleware from '../../src/middleware/auth';
6+
7+
describe('Middleware / auth', () => {
8+
describe('`token` option as string (with default `prefix` and `header`)', () => {
9+
const rnl = new RelayNetworkLayer([
10+
authMiddleware({
11+
token: '123',
12+
tokenRefreshPromise: () => 345,
13+
}),
14+
]);
15+
16+
beforeEach(() => {
17+
fetchMock.restore();
18+
19+
fetchMock.mock({
20+
matcher: '/graphql',
21+
response: {
22+
status: 200,
23+
body: { data: 'PAYLOAD' },
24+
sendAsJson: true,
25+
},
26+
method: 'POST',
27+
});
28+
});
29+
30+
it('should work with query', () => {
31+
const req1 = mockReq();
32+
return rnl.sendQueries([req1]).then(() => {
33+
assert.equal(req1.payload.response, 'PAYLOAD');
34+
const reqs = fetchMock.calls('/graphql');
35+
assert.equal(reqs.length, 1);
36+
assert.equal(reqs[0][1].headers.Authorization, 'Bearer 123');
37+
});
38+
});
39+
40+
it('should work with mutation', () => {
41+
const req1 = mockReq();
42+
return assert.isFulfilled(
43+
rnl.sendMutation(req1).then(() => {
44+
assert.equal(req1.payload.response, 'PAYLOAD');
45+
const reqs = fetchMock.calls('/graphql');
46+
assert.equal(reqs.length, 1);
47+
assert.equal(reqs[0][1].headers.Authorization, 'Bearer 123');
48+
})
49+
);
50+
});
51+
});
52+
53+
describe('`token` option as thunk (with custom `prefix` and `header`)', () => {
54+
const rnl = new RelayNetworkLayer([
55+
authMiddleware({
56+
token: () => '333',
57+
tokenRefreshPromise: () => 345,
58+
prefix: 'MyBearer ',
59+
header: 'MyAuthorization',
60+
}),
61+
]);
62+
63+
beforeEach(() => {
64+
fetchMock.restore();
65+
66+
fetchMock.mock({
67+
matcher: '/graphql',
68+
response: {
69+
status: 200,
70+
body: { data: 'PAYLOAD' },
71+
sendAsJson: true,
72+
},
73+
method: 'POST',
74+
});
75+
});
76+
77+
it('should work with query', () => {
78+
const req1 = mockReq();
79+
return rnl.sendQueries([req1]).then(() => {
80+
assert.equal(req1.payload.response, 'PAYLOAD');
81+
const reqs = fetchMock.calls('/graphql');
82+
assert.equal(reqs.length, 1);
83+
assert.equal(reqs[0][1].headers.MyAuthorization, 'MyBearer 333');
84+
});
85+
});
86+
87+
it('should work with mutation', () => {
88+
const req1 = mockReq();
89+
return assert.isFulfilled(
90+
rnl.sendMutation(req1).then(() => {
91+
assert.equal(req1.payload.response, 'PAYLOAD');
92+
const reqs = fetchMock.calls('/graphql');
93+
assert.equal(reqs.length, 1);
94+
assert.equal(reqs[0][1].headers.MyAuthorization, 'MyBearer 333');
95+
})
96+
);
97+
});
98+
});
99+
100+
describe('`tokenRefreshPromise` should be called on 401 response', () => {
101+
beforeEach(() => {
102+
fetchMock.restore();
103+
104+
fetchMock.mock({
105+
matcher: '/graphql',
106+
response: {
107+
status: 401,
108+
body: { data: 'PAYLOAD' },
109+
sendAsJson: true,
110+
},
111+
method: 'POST',
112+
});
113+
});
114+
115+
it('should work with query (provided promise)', () => {
116+
const rnl = new RelayNetworkLayer([
117+
authMiddleware({
118+
token: '123',
119+
tokenRefreshPromise: () => Promise.resolve(345),
120+
}),
121+
]);
122+
123+
const req1 = mockReq();
124+
return rnl.sendQueries([req1]).then(() => {
125+
const reqs = fetchMock.calls('/graphql');
126+
assert.equal(reqs.length, 2);
127+
assert.equal(reqs[1][1].headers.Authorization, 'Bearer 345');
128+
});
129+
});
130+
131+
it('should work with mutation (provided regular value)', () => {
132+
const rnl = new RelayNetworkLayer([
133+
authMiddleware({
134+
token: '123',
135+
tokenRefreshPromise: () => 456,
136+
}),
137+
]);
138+
139+
const req1 = mockReq();
140+
return assert.isFulfilled(
141+
rnl.sendMutation(req1).then(() => {
142+
const reqs = fetchMock.calls('/graphql');
143+
assert.equal(reqs.length, 2);
144+
assert.equal(reqs[1][1].headers.Authorization, 'Bearer 456');
145+
})
146+
);
147+
});
148+
});
149+
});

test/middleware/url.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { mockReq } from '../testutils';
55
import urlMiddleware from '../../src/middleware/url';
66

77
describe('Middleware / url', () => {
8-
describe('url option as string', () => {
8+
describe('`url` option as string', () => {
99
const rnl = new RelayNetworkLayer([
1010
urlMiddleware({
1111
url: '/other/url',
@@ -28,7 +28,7 @@ describe('Middleware / url', () => {
2828

2929
it('should work with query', () => {
3030
const req1 = mockReq();
31-
rnl.sendQueries([req1]).then(() => {
31+
return rnl.sendQueries([req1]).then(() => {
3232
assert.equal(req1.payload.response, 'PAYLOAD');
3333
});
3434
});
@@ -43,7 +43,7 @@ describe('Middleware / url', () => {
4343
});
4444
});
4545

46-
describe('url option as thunk', () => {
46+
describe('`url` option as thunk', () => {
4747
const rnl = new RelayNetworkLayer([
4848
urlMiddleware({
4949
url: (_) => '/thunk_url', // eslint-disable-line
@@ -66,7 +66,7 @@ describe('Middleware / url', () => {
6666

6767
it('should work with query', () => {
6868
const req1 = mockReq();
69-
rnl.sendQueries([req1]).then(() => {
69+
return rnl.sendQueries([req1]).then(() => {
7070
assert.equal(req1.payload.response, 'PAYLOAD');
7171
});
7272
});

0 commit comments

Comments
 (0)