mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Fix sdk auth (#3337)
* Fix format errors * Fix lint error * Fix auth token setter on server * Fix auth logout for JSON
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { AxiosInstance } from 'axios';
|
||||
import { AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||
import { AuthStorage } from '../types';
|
||||
|
||||
export type LoginCredentials = {
|
||||
@@ -24,6 +24,13 @@ export class AuthHandler {
|
||||
private storage: AuthStorage;
|
||||
private mode: 'cookie' | 'json';
|
||||
private autoRefresh: boolean;
|
||||
private autoRefreshTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
private expiresAt?: number;
|
||||
/**
|
||||
* Used for tracking if accessToken is restored from store to config.
|
||||
* Axios uses this number for interceptor. If it's number it means it's inited.
|
||||
*/
|
||||
private accessTokenInitId: number | null = null;
|
||||
|
||||
constructor(axios: AxiosInstance, options: AuthOptions) {
|
||||
this.axios = axios;
|
||||
@@ -31,6 +38,8 @@ export class AuthHandler {
|
||||
this.mode = options.mode;
|
||||
this.autoRefresh = options.autoRefresh;
|
||||
|
||||
this.accessTokenInitId = this.axios.interceptors.request.use((config) => this.initializeAccessToken(config));
|
||||
|
||||
if (this.autoRefresh) {
|
||||
this.refresh();
|
||||
}
|
||||
@@ -41,32 +50,51 @@ export class AuthHandler {
|
||||
}
|
||||
|
||||
set token(val: string | null) {
|
||||
this.axios.defaults.headers = {
|
||||
...(this.axios.defaults.headers || {}),
|
||||
Authorization: val ? `Bearer ${val}` : undefined,
|
||||
};
|
||||
if (val === null) {
|
||||
delete this.axios.defaults.headers?.Authorization;
|
||||
} else {
|
||||
this.axios.defaults.headers = {
|
||||
...(this.axios.defaults.headers || {}),
|
||||
Authorization: `Bearer ${val}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async login(credentials: LoginCredentials): Promise<{ data: AuthResponse }> {
|
||||
const response = await this.axios.post<{ data: AuthResponse }>('/auth/login', {
|
||||
...credentials,
|
||||
mode: this.mode,
|
||||
});
|
||||
this.removeTimeout();
|
||||
const response = await this.axios.post('/auth/login', { ...credentials, mode: this.mode });
|
||||
|
||||
this.token = response.data.data.access_token;
|
||||
const data = response.data.data;
|
||||
this.token = data.access_token;
|
||||
this.expiresAt = Date.now() + data.expires;
|
||||
|
||||
await this.storage.setItem('directus_access_token', this.token);
|
||||
await this.storage.setItem('directus_access_token_expires', this.expiresAt);
|
||||
|
||||
if (this.mode === 'json') {
|
||||
await this.storage.setItem('directus_refresh_token', response.data.data.refresh_token);
|
||||
await this.storage.setItem('directus_refresh_token', data.refresh_token);
|
||||
}
|
||||
|
||||
if (this.autoRefresh) {
|
||||
setTimeout(() => this.refresh(), response.data.data.expires - 10000);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async refresh(): Promise<{ data: AuthResponse }> {
|
||||
/**
|
||||
* Refresh access token 10 seconds before expiration
|
||||
*/
|
||||
async refresh(): Promise<{ data: AuthResponse } | undefined> {
|
||||
this.removeTimeout();
|
||||
|
||||
this.expiresAt = await this.storage.getItem('directus_access_token_expires');
|
||||
if (!this.expiresAt) return;
|
||||
|
||||
if (Date.now() + 10000 < this.expiresAt && this.autoRefresh) {
|
||||
this.autoRefreshTimeout = setTimeout(() => this.refresh(), this.expiresAt - Date.now() - 10000);
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: Record<string, any> = { mode: this.mode };
|
||||
|
||||
if (this.mode === 'json') {
|
||||
@@ -74,23 +102,34 @@ export class AuthHandler {
|
||||
payload['refresh_token'] = refreshToken;
|
||||
}
|
||||
|
||||
if (this.expiresAt < Date.now() + 1000) {
|
||||
this.token = null;
|
||||
}
|
||||
const response = await this.axios.post<{ data: AuthResponse }>('/auth/refresh', payload);
|
||||
|
||||
this.token = response.data.data.access_token;
|
||||
const data = response.data.data;
|
||||
this.token = data.access_token;
|
||||
this.expiresAt = Date.now() + data.expires;
|
||||
await this.storage.setItem('directus_access_token', this.token);
|
||||
await this.storage.setItem('directus_access_token_expires', this.expiresAt);
|
||||
|
||||
if (this.mode === 'json') {
|
||||
await this.storage.setItem('directus_refresh_token', response.data.data.refresh_token);
|
||||
}
|
||||
|
||||
if (this.autoRefresh) {
|
||||
setTimeout(() => this.refresh(), response.data.data.expires - 10000);
|
||||
this.autoRefreshTimeout = setTimeout(() => this.refresh(), data.expires - 10000);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
await this.axios.post('/auth/logout');
|
||||
this.removeTimeout();
|
||||
const data: Record<string, string> = {};
|
||||
if (this.mode === 'json') {
|
||||
data.refresh_token = await this.storage.getItem('directus_refresh_token');
|
||||
}
|
||||
await this.axios.post('/auth/logout', data);
|
||||
this.token = null;
|
||||
}
|
||||
|
||||
@@ -103,4 +142,31 @@ export class AuthHandler {
|
||||
await this.axios.post('/auth/password/reset', { token, password });
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* There is no prettier way to do this. We need to set access token before first request.
|
||||
* This way we intercept axios request and only first time request token from store,
|
||||
* and allows us to do new Directus(url).items(col).read() without having to handle
|
||||
* access_token restoration in methods
|
||||
*/
|
||||
private async initializeAccessToken(config: AxiosRequestConfig): Promise<AxiosRequestConfig> {
|
||||
if (this.accessTokenInitId !== null) {
|
||||
const token = await this.storage.getItem('directus_access_token');
|
||||
if (token) {
|
||||
this.token = token;
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
this.axios.interceptors.request.eject(this.accessTokenInitId);
|
||||
this.accessTokenInitId = null;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private removeTimeout(): void {
|
||||
if (this.autoRefreshTimeout !== null) {
|
||||
clearTimeout(this.autoRefreshTimeout);
|
||||
this.autoRefreshTimeout = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,17 +7,12 @@ export class ItemsHandler {
|
||||
|
||||
constructor(collection: string, axios: AxiosInstance) {
|
||||
this.axios = axios;
|
||||
this.endpoint = collection.startsWith('directus_')
|
||||
? `/${collection.substring(9)}/`
|
||||
: `/items/${collection}/`;
|
||||
this.endpoint = collection.startsWith('directus_') ? `/${collection.substring(9)}/` : `/items/${collection}/`;
|
||||
}
|
||||
|
||||
async create<T extends Item>(payload: Payload, query?: Query): Promise<Response<T>>;
|
||||
async create<T extends Item>(payloads: Payload[], query?: Query): Promise<Response<T[]>>;
|
||||
async create<T extends Item>(
|
||||
payloads: Payload | Payload[],
|
||||
query?: Query
|
||||
): Promise<Response<T | T[]>> {
|
||||
async create<T extends Item>(payloads: Payload | Payload[], query?: Query): Promise<Response<T | T[]>> {
|
||||
const result = await this.axios.post(this.endpoint, payloads, {
|
||||
params: query,
|
||||
});
|
||||
@@ -38,9 +33,7 @@ export class ItemsHandler {
|
||||
|
||||
if (
|
||||
keysOrQuery &&
|
||||
(Array.isArray(keysOrQuery) ||
|
||||
typeof keysOrQuery === 'string' ||
|
||||
typeof keysOrQuery === 'number')
|
||||
(Array.isArray(keysOrQuery) || typeof keysOrQuery === 'string' || typeof keysOrQuery === 'number')
|
||||
) {
|
||||
keys = keysOrQuery;
|
||||
}
|
||||
@@ -49,11 +42,7 @@ export class ItemsHandler {
|
||||
|
||||
if (query) {
|
||||
params = query;
|
||||
} else if (
|
||||
!query &&
|
||||
typeof keysOrQuery === 'object' &&
|
||||
Array.isArray(keysOrQuery) === false
|
||||
) {
|
||||
} else if (!query && typeof keysOrQuery === 'object' && Array.isArray(keysOrQuery) === false) {
|
||||
params = keysOrQuery as Query;
|
||||
}
|
||||
|
||||
@@ -68,16 +57,8 @@ export class ItemsHandler {
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async update<T extends Item>(
|
||||
key: PrimaryKey,
|
||||
payload: Payload,
|
||||
query?: Query
|
||||
): Promise<Response<T>>;
|
||||
async update<T extends Item>(
|
||||
keys: PrimaryKey[],
|
||||
payload: Payload,
|
||||
query?: Query
|
||||
): Promise<Response<T[]>>;
|
||||
async update<T extends Item>(key: PrimaryKey, payload: Payload, query?: Query): Promise<Response<T>>;
|
||||
async update<T extends Item>(keys: PrimaryKey[], payload: Payload, query?: Query): Promise<Response<T[]>>;
|
||||
async update<T extends Item>(payload: Payload[], query?: Query): Promise<Response<T[]>>;
|
||||
async update<T extends Item>(payload: Payload, query: Query): Promise<Response<T[]>>;
|
||||
async update<T extends Item>(
|
||||
@@ -88,8 +69,7 @@ export class ItemsHandler {
|
||||
if (
|
||||
typeof keyOrPayload === 'string' ||
|
||||
typeof keyOrPayload === 'number' ||
|
||||
(Array.isArray(keyOrPayload) &&
|
||||
(keyOrPayload as any[]).every((key) => ['string', 'number'].includes(typeof key)))
|
||||
(Array.isArray(keyOrPayload) && (keyOrPayload as any[]).every((key) => ['string', 'number'].includes(typeof key)))
|
||||
) {
|
||||
const key = keyOrPayload as PrimaryKey | PrimaryKey[];
|
||||
const payload = payloadOrQuery as Payload;
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
AuthOptions,
|
||||
RevisionsHandler,
|
||||
} from './handlers';
|
||||
import { MemoryStore } from './utils';
|
||||
import { MemoryStore, BrowserStore } from './utils';
|
||||
|
||||
class DirectusSDK {
|
||||
axios: AxiosInstance;
|
||||
@@ -27,13 +27,16 @@ class DirectusSDK {
|
||||
constructor(url: string, options?: { auth: Partial<AuthOptions> }) {
|
||||
this.axios = axios.create({
|
||||
baseURL: url,
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
this.authOptions = {
|
||||
storage: options?.auth?.storage !== undefined ? options.auth.storage : new MemoryStore(),
|
||||
mode: options?.auth?.mode !== undefined ? options.auth.mode : 'cookie',
|
||||
autoRefresh: options?.auth?.autoRefresh !== undefined ? options.auth.autoRefresh : false,
|
||||
storage: options?.auth?.storage ?? (typeof window === 'undefined' ? new MemoryStore() : new BrowserStore()),
|
||||
mode: options?.auth?.mode ?? 'cookie',
|
||||
autoRefresh: options?.auth?.autoRefresh ?? false,
|
||||
};
|
||||
|
||||
this.auth = new AuthHandler(this.axios, this.authOptions);
|
||||
}
|
||||
|
||||
// Global helpers
|
||||
@@ -49,8 +52,9 @@ class DirectusSDK {
|
||||
|
||||
// Handlers
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
auth: AuthHandler;
|
||||
|
||||
items(collection: string) {
|
||||
items(collection: string): ItemsHandler {
|
||||
if (collection.startsWith('directus_')) {
|
||||
throw new Error(`You can't read the "${collection}" collection directly.`);
|
||||
}
|
||||
@@ -58,63 +62,59 @@ class DirectusSDK {
|
||||
return new ItemsHandler(collection, this.axios);
|
||||
}
|
||||
|
||||
get activity() {
|
||||
get activity(): ActivityHandler {
|
||||
return new ActivityHandler(this.axios);
|
||||
}
|
||||
|
||||
get auth() {
|
||||
return new AuthHandler(this.axios, this.authOptions);
|
||||
}
|
||||
|
||||
get collections() {
|
||||
get collections(): CollectionsHandler {
|
||||
return new CollectionsHandler(this.axios);
|
||||
}
|
||||
|
||||
get fields() {
|
||||
get fields(): FieldsHandler {
|
||||
return new FieldsHandler(this.axios);
|
||||
}
|
||||
|
||||
get files() {
|
||||
get files(): FilesHandler {
|
||||
return new FilesHandler(this.axios);
|
||||
}
|
||||
|
||||
get folders() {
|
||||
get folders(): FoldersHandler {
|
||||
return new FoldersHandler(this.axios);
|
||||
}
|
||||
|
||||
get permissions() {
|
||||
get permissions(): PermissionsHandler {
|
||||
return new PermissionsHandler(this.axios);
|
||||
}
|
||||
|
||||
get presets() {
|
||||
get presets(): PresetsHandler {
|
||||
return new PresetsHandler(this.axios);
|
||||
}
|
||||
|
||||
get relations() {
|
||||
get relations(): RelationsHandler {
|
||||
return new RelationsHandler(this.axios);
|
||||
}
|
||||
|
||||
get revisions() {
|
||||
get revisions(): RevisionsHandler {
|
||||
return new RevisionsHandler(this.axios);
|
||||
}
|
||||
|
||||
get roles() {
|
||||
get roles(): RolesHandler {
|
||||
return new RolesHandler(this.axios);
|
||||
}
|
||||
|
||||
get server() {
|
||||
get server(): ServerHandler {
|
||||
return new ServerHandler(this.axios);
|
||||
}
|
||||
|
||||
get settings() {
|
||||
get settings(): SettingsHandler {
|
||||
return new SettingsHandler(this.axios);
|
||||
}
|
||||
|
||||
get users() {
|
||||
get users(): UsersHandler {
|
||||
return new UsersHandler(this.axios);
|
||||
}
|
||||
|
||||
get utils() {
|
||||
get utils(): UtilsHandler {
|
||||
return new UtilsHandler(this.axios);
|
||||
}
|
||||
}
|
||||
|
||||
11
packages/sdk-js/src/utils/browser-store.ts
Normal file
11
packages/sdk-js/src/utils/browser-store.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { AuthStorage } from '../types';
|
||||
|
||||
export class BrowserStore implements AuthStorage {
|
||||
async getItem(key: string): Promise<string | null> {
|
||||
return window.localStorage.getItem(key);
|
||||
}
|
||||
|
||||
async setItem(key: string, value: any): Promise<void> {
|
||||
window.localStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from './browser-store';
|
||||
export * from './memory-store';
|
||||
|
||||
@@ -30,9 +30,7 @@ describe('ActivityHandler', () => {
|
||||
|
||||
describe('read', () => {
|
||||
it('Calls ItemsHandler#read with the provided params', async () => {
|
||||
const stub = sandbox
|
||||
.stub(handler['itemsHandler'], 'read')
|
||||
.returns(Promise.resolve({ data: {} }));
|
||||
const stub = sandbox.stub(handler['itemsHandler'], 'read').returns(Promise.resolve({ data: {} }));
|
||||
|
||||
await handler.read();
|
||||
expect(stub).to.have.been.calledWith();
|
||||
@@ -50,9 +48,7 @@ describe('ActivityHandler', () => {
|
||||
|
||||
describe('comments.create', () => {
|
||||
it('Calls the /activity/comments endpoint', async () => {
|
||||
const stub = sandbox
|
||||
.stub(handler['axios'], 'post')
|
||||
.returns(Promise.resolve({ data: {} }));
|
||||
const stub = sandbox.stub(handler['axios'], 'post').returns(Promise.resolve({ data: {} }));
|
||||
|
||||
await handler.comments.create({
|
||||
collection: 'articles',
|
||||
@@ -70,9 +66,7 @@ describe('ActivityHandler', () => {
|
||||
|
||||
describe('comments.update', () => {
|
||||
it('Calls the /activity/comments/:id endpoint', async () => {
|
||||
const stub = sandbox
|
||||
.stub(handler['axios'], 'patch')
|
||||
.returns(Promise.resolve({ data: {} }));
|
||||
const stub = sandbox.stub(handler['axios'], 'patch').returns(Promise.resolve({ data: {} }));
|
||||
|
||||
await handler.comments.update(15, { comment: 'Hello Update' });
|
||||
|
||||
|
||||
@@ -123,15 +123,12 @@ describe('AuthHandler', () => {
|
||||
expect(stub).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('Calls refresh 10 seconds before expiry time when in autoRefresh mode', async () => {
|
||||
it('Calls refresh when in autoRefresh mode', async () => {
|
||||
sandbox.stub(handler['axios'], 'post').resolves({ data: mockResponse });
|
||||
handler['autoRefresh'] = true;
|
||||
const stub = sandbox.stub(handler, 'refresh').resolves();
|
||||
await handler.login({ email: 'test@example.com', password: 'test' });
|
||||
|
||||
clock.tick(885000); // 15 seconds before expiry time
|
||||
expect(stub).to.not.have.been.called;
|
||||
clock.tick(6000); // add +6s
|
||||
expect(stub).to.have.been.called;
|
||||
});
|
||||
|
||||
@@ -141,13 +138,33 @@ describe('AuthHandler', () => {
|
||||
const stub = sandbox.stub(handler, 'refresh').resolves();
|
||||
await handler.login({ email: 'test@example.com', password: 'test' });
|
||||
|
||||
clock.tick(910000);
|
||||
expect(stub).to.not.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('refresh', () => {
|
||||
it('Calls refresh if in auto refresh mode', async () => {
|
||||
const stub = sandbox.stub(AuthHandler.prototype, 'refresh');
|
||||
new AuthHandler(axiosInstance, {
|
||||
autoRefresh: true,
|
||||
storage: new MemoryStore(),
|
||||
mode: 'json',
|
||||
});
|
||||
expect(stub).to.have.been.called;
|
||||
});
|
||||
|
||||
it('Does not call refresh if not in auto refresh mode', async () => {
|
||||
const stub = sandbox.stub(AuthHandler.prototype, 'refresh');
|
||||
new AuthHandler(axiosInstance, {
|
||||
autoRefresh: false,
|
||||
storage: new MemoryStore(),
|
||||
mode: 'json',
|
||||
});
|
||||
expect(stub).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('Calls the /auth/refresh endpoint without refresh token when in cookie mode', async () => {
|
||||
sandbox.stub(handler['storage'], 'getItem').resolves(Date.now() + 9000);
|
||||
const stub = sandbox.stub(handler['axios'], 'post').resolves({ data: mockResponse });
|
||||
handler['mode'] = 'cookie';
|
||||
await handler.refresh();
|
||||
@@ -159,6 +176,7 @@ describe('AuthHandler', () => {
|
||||
handler['mode'] = 'json';
|
||||
const testStore = new MemoryStore();
|
||||
testStore['values'].directus_refresh_token = 'test-token';
|
||||
testStore['values'].directus_access_token_expires = Date.now() + 9000;
|
||||
handler['storage'] = testStore;
|
||||
await handler.refresh();
|
||||
expect(stub).to.have.been.calledWith('/auth/refresh', {
|
||||
@@ -168,6 +186,7 @@ describe('AuthHandler', () => {
|
||||
});
|
||||
|
||||
it('Sets the token on refresh', async () => {
|
||||
sandbox.stub(handler['storage'], 'getItem').resolves(Date.now() + 9000);
|
||||
sandbox.stub(handler['axios'], 'post').resolves({ data: mockResponse });
|
||||
handler.token = 'before';
|
||||
await handler.refresh();
|
||||
@@ -178,6 +197,7 @@ describe('AuthHandler', () => {
|
||||
sandbox.stub(handler['axios'], 'post').resolves({ data: mockResponse });
|
||||
const testStore = new MemoryStore();
|
||||
const stub = sandbox.stub(testStore, 'setItem');
|
||||
sandbox.stub(testStore, 'getItem').resolves(Date.now() + 9000);
|
||||
|
||||
handler['storage'] = testStore;
|
||||
|
||||
@@ -187,6 +207,13 @@ describe('AuthHandler', () => {
|
||||
});
|
||||
|
||||
it('Calls itself 10 seconds before expiry when autoRefresh is enabled', async () => {
|
||||
const store = new MemoryStore();
|
||||
const getItem = sandbox.stub(store, 'getItem').resolves(Date.now() + 9000);
|
||||
handler = new AuthHandler(axiosInstance, {
|
||||
mode: 'json',
|
||||
autoRefresh: true,
|
||||
storage: store,
|
||||
});
|
||||
sandbox.stub(handler['axios'], 'post').resolves({ data: mockResponse });
|
||||
handler['autoRefresh'] = true;
|
||||
const spy = sandbox.spy(handler, 'refresh');
|
||||
@@ -195,6 +222,45 @@ describe('AuthHandler', () => {
|
||||
|
||||
expect(spy).to.have.been.calledTwice;
|
||||
});
|
||||
|
||||
it('Does not refresh if there is no access token', async () => {
|
||||
sandbox.stub(handler['storage'], 'getItem').resolves();
|
||||
const post = sandbox.stub(handler['axios'], 'post').resolves();
|
||||
await handler.refresh();
|
||||
expect(post).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('Does not refresh if there is more then 10 seconds in access token', async () => {
|
||||
handler['autoRefresh'] = true;
|
||||
sandbox.stub(handler['storage'], 'getItem').resolves(Date.now() + 11000);
|
||||
const post = sandbox.stub(handler['axios'], 'post').resolves();
|
||||
await handler.refresh();
|
||||
expect(post).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('Calls refresh if there is less then 10 seconds in access token', async () => {
|
||||
sandbox.stub(handler['storage'], 'getItem').resolves(Date.now() + 9000);
|
||||
const post = sandbox.stub(handler['axios'], 'post').resolves({ data: mockResponse });
|
||||
await handler.refresh();
|
||||
expect(post).to.have.been.called;
|
||||
});
|
||||
|
||||
it('Sets timeout if there is more then 10 seconds left in access token', async () => {
|
||||
handler['autoRefresh'] = true;
|
||||
expect(handler['autoRefreshTimeout']).to.be.null;
|
||||
sandbox.stub(handler['storage'], 'getItem').resolves(Date.now() + 11000);
|
||||
await handler.refresh();
|
||||
expect(handler['autoRefreshTimeout']).to.not.be.null;
|
||||
});
|
||||
|
||||
it('Calls refresh again after timeout is passed', async () => {
|
||||
handler['autoRefresh'] = true;
|
||||
sandbox.stub(handler['storage'], 'getItem').resolves(Date.now() + 11000);
|
||||
await handler.refresh();
|
||||
const refresh = sandbox.stub(handler, 'refresh').resolves();
|
||||
clock.tick(2000);
|
||||
expect(refresh).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
describe('logout', () => {
|
||||
@@ -232,4 +298,33 @@ describe('AuthHandler', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('initializeAccessToken', async () => {
|
||||
it('Initializes only once', async () => {
|
||||
const stub = sandbox.stub(handler['storage'], 'getItem').resolves();
|
||||
expect(handler['accessTokenInitId']).to.not.be.null;
|
||||
expect(stub).to.not.have.been.called;
|
||||
await handler['initializeAccessToken']({ headers: {} });
|
||||
expect(handler['accessTokenInitId']).to.be.null;
|
||||
expect(stub).to.have.been.calledOnce;
|
||||
await handler['initializeAccessToken']({ headers: {} });
|
||||
expect(handler['accessTokenInitId']).to.be.null;
|
||||
expect(stub).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('Sets access token from storage', async () => {
|
||||
const stub = sandbox.stub(handler['storage'], 'getItem').resolves('token');
|
||||
const tokenSpy = sinon.spy(handler, 'token', ['get', 'set']);
|
||||
await handler['initializeAccessToken']({ headers: {} });
|
||||
expect(stub).to.have.been.calledWith('directus_access_token');
|
||||
expect(tokenSpy.set).to.be.calledWith('token');
|
||||
});
|
||||
|
||||
it('Changes Authorization header in provided config', async () => {
|
||||
const config = { headers: {} };
|
||||
const stub = sandbox.stub(handler['storage'], 'getItem').resolves('token');
|
||||
await handler['initializeAccessToken'](config);
|
||||
expect(config.headers['Authorization']).to.equal('Bearer token');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
|
||||
import { expect } from 'chai';
|
||||
import { MemoryStore } from '../src/utils';
|
||||
import { BrowserStore } from '../src/utils/browser-store';
|
||||
|
||||
describe('DirectusSDK', () => {
|
||||
let directus: DirectusSDK;
|
||||
@@ -31,7 +32,14 @@ describe('DirectusSDK', () => {
|
||||
});
|
||||
|
||||
it('Sets the passed authOptions', () => {
|
||||
const fakeStore = { async getItem() {}, async setItem() {} };
|
||||
const fakeStore = {
|
||||
async getItem() {
|
||||
return;
|
||||
},
|
||||
async setItem() {
|
||||
return;
|
||||
},
|
||||
};
|
||||
const directusWithOptions = new DirectusSDK('http://example.com', {
|
||||
auth: {
|
||||
autoRefresh: false,
|
||||
@@ -51,6 +59,20 @@ describe('DirectusSDK', () => {
|
||||
expect(directus['authOptions'].storage).to.be.instanceOf(MemoryStore);
|
||||
});
|
||||
|
||||
it('Defaults to the BrowserStore in browser, and MemoryStore in Node', () => {
|
||||
const defaultWindow = globalThis.window;
|
||||
|
||||
globalThis.window = undefined;
|
||||
let customDirectus = new DirectusSDK('http://example.com');
|
||||
expect(customDirectus['authOptions'].storage).to.be.instanceOf(MemoryStore);
|
||||
|
||||
globalThis.window = {} as any;
|
||||
customDirectus = new DirectusSDK('http://example.com');
|
||||
expect(customDirectus['authOptions'].storage).to.be.instanceOf(BrowserStore);
|
||||
|
||||
globalThis.window = defaultWindow;
|
||||
});
|
||||
|
||||
it('Gets / Sets URL', () => {
|
||||
expect(directus.url).to.equal('http://example.com');
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MemoryStore } from '../src/utils/';
|
||||
import { MemoryStore, BrowserStore } from '../src/utils/';
|
||||
import { expect } from 'chai';
|
||||
import sinon from 'sinon';
|
||||
|
||||
describe('Utils', () => {
|
||||
describe('MemoryStore', () => {
|
||||
@@ -16,4 +17,24 @@ describe('Utils', () => {
|
||||
expect(store['values'].test).to.equal('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('BrowserStore', () => {
|
||||
beforeEach(() => {
|
||||
globalThis.window = {
|
||||
localStorage: { getItem: sinon.spy(), setItem: sinon.spy() },
|
||||
} as any;
|
||||
});
|
||||
|
||||
it('Gets values based on key', async () => {
|
||||
const store = new BrowserStore();
|
||||
await store.getItem('test');
|
||||
expect(globalThis.window.localStorage.getItem).to.be.calledWith('test');
|
||||
});
|
||||
|
||||
it('Sets value based on key', async () => {
|
||||
const store = new BrowserStore();
|
||||
await store.setItem('key', 'value');
|
||||
expect(globalThis.window.localStorage.setItem).to.be.calledWith('key', 'value');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["es5"],
|
||||
"module": "es2015",
|
||||
"lib": ["es5", "DOM"],
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
|
||||
Reference in New Issue
Block a user