Skip to content
This repository was archived by the owner on Sep 1, 2022. It is now read-only.

Commit 906999e

Browse files
committed
feat(authentication): persist the token
Storing on cookie or localStorage Closes #3, #7
1 parent b764734 commit 906999e

File tree

3 files changed

+190
-10
lines changed

3 files changed

+190
-10
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@
5353
},
5454
"dependencies": {
5555
"axios": "^0.18.0",
56+
"js-cookie": "^2.2.0",
5657
"qs": "^6.5.1"
5758
},
5859
"devDependencies": {
60+
"@types/js-cookie": "^2.1.0",
5961
"@types/qs": "^6.5.1",
6062
"@types/sinon": "^4.3.1",
6163
"ava": "^1.0.0-beta.4",

src/lib/sdk.spec.ts

Lines changed: 105 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,65 @@ test('Create an instance', t => {
4040
'getFile',
4141
'upload',
4242
'setToken',
43-
'clearToken'
43+
'clearToken',
44+
'isBrowser'
4445
]
4546
);
4647

47-
t.deepEqual(Object.getOwnPropertyNames(t.context.strapi), ['axios']);
48+
t.deepEqual(Object.getOwnPropertyNames(t.context.strapi), [
49+
'axios',
50+
'storeConfig'
51+
]);
4852

4953
t.deepEqual(t.context.strapi.axios.defaults.baseURL, 'http://strapi-host');
5054
});
5155

56+
test.serial('Create an instance with existing token on localStorage', t => {
57+
browserEnv(['window']);
58+
const globalAny: any = global;
59+
globalAny.window.localStorage = storageMock();
60+
const setItem = sinon.spy(globalAny.window.localStorage, 'setItem');
61+
globalAny.window.localStorage.setItem('jwt', '"XXX"');
62+
const strapi = new Strapi('http://strapi-host', {
63+
cookie: false
64+
});
65+
66+
t.is(strapi.axios.defaults.headers.common.Authorization, 'Bearer XXX');
67+
t.true(setItem.calledWith('jwt', '"XXX"'));
68+
delete strapi.axios.defaults.headers.common.Authorization;
69+
delete globalAny.window;
70+
});
71+
72+
test('Create an instance with existing token on cookies', t => {
73+
browserEnv(['window', 'document']);
74+
const Cookies = require('js-cookie');
75+
const globalAny: any = global;
76+
Cookies.set('jwt', 'XXX');
77+
// const CookieGet = sinon.spy(Cookies)
78+
79+
const strapi = new Strapi('http://strapi-host', {
80+
localStorage: false
81+
});
82+
83+
t.is(strapi.axios.defaults.headers.common.Authorization, 'Bearer XXX');
84+
// TODO: Mock Cookies
85+
// t.true(CookieGet.calledWith('jwt'));
86+
delete strapi.axios.defaults.headers.common.Authorization;
87+
delete globalAny.window;
88+
});
89+
90+
test.serial('Create an instance without token', t => {
91+
browserEnv(['window']);
92+
const globalAny: any = global;
93+
const strapi = new Strapi('http://strapi-host', {
94+
cookie: false,
95+
localStorage: false
96+
});
97+
98+
t.is(strapi.axios.defaults.headers.common.Authorization, undefined);
99+
delete globalAny.window;
100+
});
101+
52102
test('Make a request', async t => {
53103
t.context.axiosRequest.resolves({
54104
data: [{ foo: 'bar' }]
@@ -243,19 +293,18 @@ test('Provider authentication on Node.js', async t => {
243293
});
244294

245295
test.serial('Provider authentication on browser', async t => {
246-
const globalAny: any = global;
247296
browserEnv(['window'], {
248297
url: 'http://localhost?access_token=XXX'
249298
});
299+
const globalAny: any = global;
300+
globalAny.window.localStorage = storageMock();
250301
t.context.axiosRequest.resolves({
251302
data: {
252303
jwt: 'foo',
253304
user: {}
254305
}
255306
});
256-
const authentication = await t.context.strapi.authenticateProvider(
257-
'github'
258-
);
307+
const authentication = await t.context.strapi.authenticateProvider('github');
259308

260309
t.true(
261310
t.context.axiosRequest.calledWithExactly({
@@ -401,3 +450,53 @@ test('Set token', t => {
401450
'Bearer foo'
402451
);
403452
});
453+
454+
test('Set token on Node.js', t => {
455+
browserEnv(['window', 'document']);
456+
// const Cookies = require('js-cookie');
457+
const globalAny: any = global;
458+
globalAny.window.localStorage = storageMock();
459+
const setItem = sinon.spy(globalAny.window.localStorage, 'setItem');
460+
// const CookieSet = sinon.spy(Cookies, 'set')
461+
462+
const strapi = new Strapi('http://strapi-host', {
463+
cookie: false,
464+
localStorage: false
465+
});
466+
strapi.setToken('XXX');
467+
468+
t.is(strapi.axios.defaults.headers.common.Authorization, 'Bearer XXX');
469+
t.true(setItem.notCalled);
470+
// t.true(CookieSet.notCalled)
471+
delete globalAny.window;
472+
});
473+
474+
test('Clear token without storage', t => {
475+
browserEnv(['window']);
476+
const globalAny: any = global;
477+
globalAny.window.localStorage = storageMock();
478+
const setItem = sinon.spy(globalAny.window.localStorage, 'setItem');
479+
const strapi = new Strapi('http://strapi-host', {
480+
cookie: false,
481+
localStorage: false
482+
});
483+
strapi.axios.defaults.headers.common.Authorization = 'Bearer XXX';
484+
strapi.clearToken();
485+
t.true(setItem.notCalled);
486+
t.is(strapi.axios.defaults.headers.common.Authorization, undefined);
487+
});
488+
489+
function storageMock() {
490+
const storage: any = {};
491+
return {
492+
setItem(key: string, value: string) {
493+
storage[key] = value || '';
494+
},
495+
getItem(key: string) {
496+
return key in storage ? storage[key] : null;
497+
},
498+
removeItem(key: string) {
499+
delete storage[key];
500+
}
501+
};
502+
}

src/lib/sdk.ts

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
2+
import * as Cookies from 'js-cookie';
23
import * as qs from 'qs';
34

45
export interface Authentication {
@@ -14,20 +15,65 @@ export interface Token {
1415
oauth_token?: string;
1516
}
1617

18+
export interface CookieConfig {
19+
key: string;
20+
options: object;
21+
}
22+
23+
export interface LocalStorageConfig {
24+
key: string;
25+
}
26+
27+
export interface StoreConfig {
28+
cookie?: CookieConfig | false;
29+
localStorage?: LocalStorageConfig | false;
30+
}
31+
1732
export default class Strapi {
1833
public axios: AxiosInstance;
34+
public storeConfig: StoreConfig;
1935

2036
/**
2137
* Default constructor.
2238
* @param baseURL Your Strapi host.
2339
* @param axiosConfig Extend Axios configuration.
2440
*/
25-
constructor(baseURL: string, requestConfig?: AxiosRequestConfig) {
41+
constructor(
42+
baseURL: string,
43+
storeConfig?: StoreConfig,
44+
requestConfig?: AxiosRequestConfig
45+
) {
2646
this.axios = axios.create({
2747
baseURL,
2848
paramsSerializer: qs.stringify,
2949
...requestConfig
3050
});
51+
this.storeConfig = {
52+
cookie: {
53+
key: 'jwt',
54+
options: {
55+
path: '/'
56+
}
57+
},
58+
localStorage: {
59+
key: 'jwt'
60+
},
61+
...storeConfig
62+
};
63+
64+
if (this.isBrowser()) {
65+
let existingToken;
66+
if (this.storeConfig.cookie) {
67+
existingToken = Cookies.get(this.storeConfig.cookie.key);
68+
} else if (this.storeConfig.localStorage) {
69+
existingToken = JSON.parse(window.localStorage.getItem(
70+
this.storeConfig.localStorage.key
71+
) as string);
72+
}
73+
if (existingToken) {
74+
this.setToken(existingToken, true);
75+
}
76+
}
3177
}
3278

3379
/**
@@ -163,7 +209,7 @@ export default class Strapi {
163209
): Promise<Authentication> {
164210
this.clearToken();
165211
// Handling browser query
166-
if (typeof window !== 'undefined') {
212+
if (this.isBrowser()) {
167213
params = qs.parse(window.location.search, { ignoreQueryPrefix: true });
168214
}
169215
const authentication: Authentication = await this.request(
@@ -285,14 +331,47 @@ export default class Strapi {
285331
* Set token on Axios configuration
286332
* @param token Retrieved by register or login
287333
*/
288-
public setToken(token: string): void {
334+
public setToken(token: string, comesFromStorage?: boolean): void {
289335
this.axios.defaults.headers.common.Authorization = 'Bearer ' + token;
336+
if (this.isBrowser() && !comesFromStorage) {
337+
if (this.storeConfig.localStorage) {
338+
window.localStorage.setItem(
339+
this.storeConfig.localStorage.key,
340+
JSON.stringify(token)
341+
);
342+
}
343+
if (this.storeConfig.cookie) {
344+
Cookies.set(
345+
this.storeConfig.cookie.key,
346+
token,
347+
this.storeConfig.cookie.options
348+
);
349+
}
350+
}
290351
}
291352

292353
/**
293354
* Remove token from Axios configuration
294355
*/
295-
private clearToken(): void {
356+
public clearToken(): void {
296357
delete this.axios.defaults.headers.common.Authorization;
358+
if (this.isBrowser()) {
359+
if (this.storeConfig.localStorage) {
360+
window.localStorage.removeItem(this.storeConfig.localStorage.key);
361+
}
362+
if (this.storeConfig.cookie) {
363+
Cookies.remove(
364+
this.storeConfig.cookie.key,
365+
this.storeConfig.cookie.options
366+
);
367+
}
368+
}
369+
}
370+
371+
/**
372+
* Check if it runs on browser
373+
*/
374+
private isBrowser(): boolean {
375+
return typeof window !== 'undefined';
297376
}
298377
}

0 commit comments

Comments
 (0)