Skip to content

Commit

Permalink
fix(AuthMiddleware): Fix catching 401 error, add tests
Browse files Browse the repository at this point in the history
Closes #42
  • Loading branch information
nodkz committed Mar 20, 2017
1 parent 6d8f5a2 commit 87f2b87
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 12 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Middlewares
- `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`).
- **authMiddleware** - for adding auth token, and refreshing it if gets 401 response from server.
- `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).
- `tokenRefreshPromise`: - function(req, err) which must return promise with new token, called only if server returns 401 status code and this function is provided.
- `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.
- `allowEmptyToken` - allow made a request without Authorization header if token is empty (default: `false`).
- `prefix` - prefix before token (default: `'Bearer '`).
- `header` - name of the HTTP header to pass the token in (default: `'Authorization'`).
Expand Down
22 changes: 15 additions & 7 deletions src/middleware/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,26 @@ export default function authMiddleware(opts = {}) {
}
return next(req);
})
.then(res => {
if (res.status === 401 && tokenRefreshPromise) {
throw new WrongTokenError('Received status 401 from server', res);
.catch(err => {
if (
err &&
err.fetchResponse &&
err.fetchResponse.status === 401 &&
tokenRefreshPromise
) {
throw new WrongTokenError(
'Received status 401 from server',
err.fetchResponse
);
} else {
throw err;
}
return res;
})
.catch(err => {
if (err.name === 'WrongTokenError') {
if (!tokenRefreshInProgress) {
tokenRefreshInProgress = tokenRefreshPromise(
req,
err.res
tokenRefreshInProgress = Promise.resolve(
tokenRefreshPromise(req, err.res)
).then(newToken => {
tokenRefreshInProgress = null;
return newToken;
Expand Down
149 changes: 149 additions & 0 deletions test/middleware/auth.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { assert } from 'chai';
import fetchMock from 'fetch-mock';
import { RelayNetworkLayer } from '../../src';
import { mockReq } from '../testutils';
import authMiddleware from '../../src/middleware/auth';

describe('Middleware / auth', () => {
describe('`token` option as string (with default `prefix` and `header`)', () => {
const rnl = new RelayNetworkLayer([
authMiddleware({
token: '123',
tokenRefreshPromise: () => 345,
}),
]);

beforeEach(() => {
fetchMock.restore();

fetchMock.mock({
matcher: '/graphql',
response: {
status: 200,
body: { data: 'PAYLOAD' },
sendAsJson: true,
},
method: 'POST',
});
});

it('should work with query', () => {
const req1 = mockReq();
return rnl.sendQueries([req1]).then(() => {
assert.equal(req1.payload.response, 'PAYLOAD');
const reqs = fetchMock.calls('/graphql');
assert.equal(reqs.length, 1);
assert.equal(reqs[0][1].headers.Authorization, 'Bearer 123');
});
});

it('should work with mutation', () => {
const req1 = mockReq();
return assert.isFulfilled(
rnl.sendMutation(req1).then(() => {
assert.equal(req1.payload.response, 'PAYLOAD');
const reqs = fetchMock.calls('/graphql');
assert.equal(reqs.length, 1);
assert.equal(reqs[0][1].headers.Authorization, 'Bearer 123');
})
);
});
});

describe('`token` option as thunk (with custom `prefix` and `header`)', () => {
const rnl = new RelayNetworkLayer([
authMiddleware({
token: () => '333',
tokenRefreshPromise: () => 345,
prefix: 'MyBearer ',
header: 'MyAuthorization',
}),
]);

beforeEach(() => {
fetchMock.restore();

fetchMock.mock({
matcher: '/graphql',
response: {
status: 200,
body: { data: 'PAYLOAD' },
sendAsJson: true,
},
method: 'POST',
});
});

it('should work with query', () => {
const req1 = mockReq();
return rnl.sendQueries([req1]).then(() => {
assert.equal(req1.payload.response, 'PAYLOAD');
const reqs = fetchMock.calls('/graphql');
assert.equal(reqs.length, 1);
assert.equal(reqs[0][1].headers.MyAuthorization, 'MyBearer 333');
});
});

it('should work with mutation', () => {
const req1 = mockReq();
return assert.isFulfilled(
rnl.sendMutation(req1).then(() => {
assert.equal(req1.payload.response, 'PAYLOAD');
const reqs = fetchMock.calls('/graphql');
assert.equal(reqs.length, 1);
assert.equal(reqs[0][1].headers.MyAuthorization, 'MyBearer 333');
})
);
});
});

describe('`tokenRefreshPromise` should be called on 401 response', () => {
beforeEach(() => {
fetchMock.restore();

fetchMock.mock({
matcher: '/graphql',
response: {
status: 401,
body: { data: 'PAYLOAD' },
sendAsJson: true,
},
method: 'POST',
});
});

it('should work with query (provided promise)', () => {
const rnl = new RelayNetworkLayer([
authMiddleware({
token: '123',
tokenRefreshPromise: () => Promise.resolve(345),
}),
]);

const req1 = mockReq();
return rnl.sendQueries([req1]).then(() => {
const reqs = fetchMock.calls('/graphql');
assert.equal(reqs.length, 2);
assert.equal(reqs[1][1].headers.Authorization, 'Bearer 345');
});
});

it('should work with mutation (provided regular value)', () => {
const rnl = new RelayNetworkLayer([
authMiddleware({
token: '123',
tokenRefreshPromise: () => 456,
}),
]);

const req1 = mockReq();
return assert.isFulfilled(
rnl.sendMutation(req1).then(() => {
const reqs = fetchMock.calls('/graphql');
assert.equal(reqs.length, 2);
assert.equal(reqs[1][1].headers.Authorization, 'Bearer 456');
})
);
});
});
});
8 changes: 4 additions & 4 deletions test/middleware/url.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { mockReq } from '../testutils';
import urlMiddleware from '../../src/middleware/url';

describe('Middleware / url', () => {
describe('url option as string', () => {
describe('`url` option as string', () => {
const rnl = new RelayNetworkLayer([
urlMiddleware({
url: '/other/url',
Expand All @@ -28,7 +28,7 @@ describe('Middleware / url', () => {

it('should work with query', () => {
const req1 = mockReq();
rnl.sendQueries([req1]).then(() => {
return rnl.sendQueries([req1]).then(() => {
assert.equal(req1.payload.response, 'PAYLOAD');
});
});
Expand All @@ -43,7 +43,7 @@ describe('Middleware / url', () => {
});
});

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

it('should work with query', () => {
const req1 = mockReq();
rnl.sendQueries([req1]).then(() => {
return rnl.sendQueries([req1]).then(() => {
assert.equal(req1.payload.response, 'PAYLOAD');
});
});
Expand Down

0 comments on commit 87f2b87

Please sign in to comment.