Skip to content

Commit b128826

Browse files
committed
Test automatic refresh token, fix missing auth headers
1 parent aa92a90 commit b128826

File tree

2 files changed

+168
-8
lines changed

2 files changed

+168
-8
lines changed

src/SlicknodeLink.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,12 +315,16 @@ export default class SlicknodeLink extends ApolloLink {
315315
},
316316
next: (result) => {
317317
if (result.data && result.data.refreshAuthToken) {
318-
this.validateAndSetAuthTokenSet(result.data.refreshAuthToken);
318+
if (!this.validateAndSetAuthTokenSet(result.data.refreshAuthToken)) {
319+
this.logout();
320+
}
319321
} else {
320322
this.debug('Refreshing auth token mutation failed');
321323
this.logout();
322324
}
323-
resolve({});
325+
resolve(this.hasAccessToken() ? {
326+
Authorization: `Bearer ${this.getAccessToken()}`,
327+
} : {});
324328
},
325329
});
326330
} else {

src/__tests__/index-test.ts

Lines changed: 162 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ describe('SlicknodeLink', () => {
185185
});
186186
});
187187

188-
it('executes refreshToken request in subsequent links', (done) => {
188+
it('executes refreshToken request in subsequent links and adds auth headers', (done) => {
189189
const data = {
190190
test: true,
191191
};
@@ -195,22 +195,177 @@ describe('SlicknodeLink', () => {
195195
refreshToken: 'refresh1',
196196
refreshTokenLifetime: 100,
197197
};
198+
const refreshedAuthTokenSet: IAuthTokenSet = {
199+
accessToken: 'accessToken2',
200+
accessTokenLifetime: 20,
201+
refreshToken: 'refresh2',
202+
refreshTokenLifetime: 200,
203+
};
198204
const slicknodeLink = new SlicknodeLink();
199205
slicknodeLink.setAuthTokenSet(authTokenSet);
200206

207+
let refreshTokenExecuted = false;
208+
const query = gql`{test}`;
201209
const link = ApolloLink.from([
202210
slicknodeLink,
203211
new ApolloLink((operation) => {
204-
expect(operation.query).to.deep.equal(gql`${REFRESH_TOKEN_MUTATION}`);
205-
expect(operation.variables).to.deep.equal({
206-
token: authTokenSet.refreshToken,
212+
if (!refreshTokenExecuted) {
213+
// First request refreshes auth tokens
214+
expect(operation.query).to.deep.equal(gql`${REFRESH_TOKEN_MUTATION}`);
215+
expect(operation.variables).to.deep.equal({
216+
token: authTokenSet.refreshToken,
217+
});
218+
expect(operation.getContext()).to.deep.equal({});
219+
refreshTokenExecuted = true;
220+
return new Observable<FetchResult>((observer) => {
221+
observer.next({
222+
data: {
223+
refreshAuthToken: refreshedAuthTokenSet,
224+
},
225+
});
226+
});
227+
}
228+
// Second request returns actual results and should contain auth headers
229+
expect(operation.query).to.deep.equal(query);
230+
expect(operation.variables).to.deep.equal({});
231+
expect(operation.getContext()).to.deep.equal({
232+
headers: {
233+
Authorization: `Bearer ${refreshedAuthTokenSet.accessToken}`,
234+
},
207235
});
208-
expect(operation.getContext()).to.deep.equal({});
236+
return new Observable<FetchResult>((observer) => {
237+
observer.next({data});
238+
});
239+
}),
240+
]);
241+
const request: GraphQLRequest = {
242+
query,
243+
variables: {},
244+
};
245+
const observable = execute(link, request);
246+
observable.subscribe({
247+
next(result: FetchResult) {
248+
expect(result.data).to.equal(data);
209249
done();
210-
return null;
250+
},
251+
error: done,
252+
});
253+
});
254+
255+
it('ignores invalid refreshToken', (done) => {
256+
const data = {
257+
test: true,
258+
};
259+
const authTokenSet: IAuthTokenSet = {
260+
accessToken: 'accessToken1',
261+
accessTokenLifetime: -20,
262+
refreshToken: 'refresh1',
263+
refreshTokenLifetime: 100,
264+
};
265+
const refreshedAuthTokenSet = {
266+
accessToken: 'accessToken2',
267+
accessTokenLifetime: 'invalid',
268+
refreshToken: 'refresh2',
269+
refreshTokenLifetime: 200,
270+
};
271+
const slicknodeLink = new SlicknodeLink();
272+
slicknodeLink.setAuthTokenSet(authTokenSet);
273+
274+
let refreshTokenExecuted = false;
275+
const query = gql`{test}`;
276+
const link = ApolloLink.from([
277+
slicknodeLink,
278+
new ApolloLink((operation) => {
279+
if (!refreshTokenExecuted) {
280+
// First request refreshes auth tokens
281+
expect(operation.query).to.deep.equal(gql`${REFRESH_TOKEN_MUTATION}`);
282+
expect(operation.variables).to.deep.equal({
283+
token: authTokenSet.refreshToken,
284+
});
285+
expect(operation.getContext()).to.deep.equal({});
286+
refreshTokenExecuted = true;
287+
return new Observable<FetchResult>((observer) => {
288+
observer.next({
289+
data: {
290+
refreshAuthToken: refreshedAuthTokenSet,
291+
},
292+
});
293+
});
294+
}
295+
// Second request returns actual results and should contain auth headers
296+
expect(operation.query).to.deep.equal(query);
297+
expect(operation.variables).to.deep.equal({});
298+
expect(operation.getContext()).to.deep.equal({headers: {}});
299+
expect(slicknodeLink.getRefreshToken()).to.be.null;
300+
return new Observable<FetchResult>((observer) => {
301+
observer.next({data});
302+
});
211303
}),
212304
]);
305+
const request: GraphQLRequest = {
306+
query,
307+
variables: {},
308+
};
309+
const observable = execute(link, request);
310+
observable.subscribe({
311+
next(result: FetchResult) {
312+
expect(result.data).to.equal(data);
313+
done();
314+
},
315+
error: done,
316+
});
317+
});
318+
319+
it('ignores expired accessToken from automatic refresh', (done) => {
320+
const data = {
321+
test: true,
322+
};
323+
const authTokenSet: IAuthTokenSet = {
324+
accessToken: 'accessToken1',
325+
accessTokenLifetime: -20,
326+
refreshToken: 'refresh1',
327+
refreshTokenLifetime: 100,
328+
};
329+
const refreshedAuthTokenSet = {
330+
accessToken: 'accessToken2',
331+
accessTokenLifetime: -20,
332+
refreshToken: 'refresh2',
333+
refreshTokenLifetime: 200,
334+
};
335+
const slicknodeLink = new SlicknodeLink();
336+
slicknodeLink.setAuthTokenSet(authTokenSet);
337+
338+
let refreshTokenExecuted = false;
213339
const query = gql`{test}`;
340+
const link = ApolloLink.from([
341+
slicknodeLink,
342+
new ApolloLink((operation) => {
343+
if (!refreshTokenExecuted) {
344+
// First request refreshes auth tokens
345+
expect(operation.query).to.deep.equal(gql`${REFRESH_TOKEN_MUTATION}`);
346+
expect(operation.variables).to.deep.equal({
347+
token: authTokenSet.refreshToken,
348+
});
349+
expect(operation.getContext()).to.deep.equal({});
350+
refreshTokenExecuted = true;
351+
return new Observable<FetchResult>((observer) => {
352+
observer.next({
353+
data: {
354+
refreshAuthToken: refreshedAuthTokenSet,
355+
},
356+
});
357+
});
358+
}
359+
// Second request returns actual results and should contain auth headers
360+
expect(operation.query).to.deep.equal(query);
361+
expect(operation.variables).to.deep.equal({});
362+
expect(operation.getContext()).to.deep.equal({headers: {}});
363+
expect(slicknodeLink.hasAccessToken()).to.be.false;
364+
return new Observable<FetchResult>((observer) => {
365+
observer.next({data});
366+
});
367+
}),
368+
]);
214369
const request: GraphQLRequest = {
215370
query,
216371
variables: {},
@@ -219,6 +374,7 @@ describe('SlicknodeLink', () => {
219374
observable.subscribe({
220375
next(result: FetchResult) {
221376
expect(result.data).to.equal(data);
377+
done();
222378
},
223379
error: done,
224380
});

0 commit comments

Comments
 (0)