HM-83. Добавлены классы для работы с авторизацией (#36)
This commit is contained in:
45
src/api/AuthServiceAPI.js
Normal file
45
src/api/AuthServiceAPI.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import {AUTH_SERVICE, AUTH_ENDPOINTS} from './consts';
|
||||||
|
import tokenApi from './TokenAPI';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Api для выполнения запросов авторизации
|
||||||
|
* @class AuthServiceApi
|
||||||
|
*/
|
||||||
|
class AuthServiceApi {
|
||||||
|
constructor () {
|
||||||
|
this.URL = AUTH_SERVICE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выполняет запрос авторизации и если он успешен, то добавляет токены в память
|
||||||
|
* @param {string} login - логин пользователя
|
||||||
|
* @param {string} password - пароль пользователя
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
auth = (login, password) => {
|
||||||
|
return axios.post(`${this.URL}${AUTH_ENDPOINTS.AUTH}`, {
|
||||||
|
login,
|
||||||
|
password,
|
||||||
|
}).then(({data: tokens}) => {
|
||||||
|
tokenApi.saveTokenPair(tokens);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Пытается реврешнуть пару токенов и в случае успеха обновляет их в памяти
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
refresh = () => {
|
||||||
|
const refresh_token = tokenApi.getRefreshToken();
|
||||||
|
return axios.post(`${this.URL}${AUTH_ENDPOINTS.REFRESH}`, {refresh_token})
|
||||||
|
.then(({data: tokens}) => {
|
||||||
|
tokenApi.saveTokenPair(tokens);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const authServiceApi = new AuthServiceApi();
|
||||||
|
|
||||||
|
export default authServiceApi;
|
||||||
92
src/api/HttpAPI.js
Normal file
92
src/api/HttpAPI.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import {makeUrlWithQuery} from '../utils/urlUtils';
|
||||||
|
import httpAuthApi from './HttpAuthAPI';
|
||||||
|
|
||||||
|
const GET = 'GET';
|
||||||
|
const POST = 'POST';
|
||||||
|
const PUT = 'PUT';
|
||||||
|
const DELETE = 'DELETE';
|
||||||
|
const HEAD = 'HEAD';
|
||||||
|
const OPTIONS = 'OPTIONS';
|
||||||
|
const PATH = 'PATH';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Методы запросов к серверу
|
||||||
|
* @interface MethodType
|
||||||
|
* @type {'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'PATH'}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Api для выполнения запросов к серверу
|
||||||
|
* @class HttpApi
|
||||||
|
*/
|
||||||
|
class HttpApi {
|
||||||
|
/**
|
||||||
|
* Общий метод выполнения запроса
|
||||||
|
* @param {MethodType} method - метод запроса
|
||||||
|
* @param {string} url - адрес запроса
|
||||||
|
* @param {Object} body - тело запроса
|
||||||
|
*/
|
||||||
|
request = (method, url, body) => {
|
||||||
|
return httpAuthApi.request({method, url, body});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url - адрес запроса
|
||||||
|
* @param {Object<string, string>} query - параметры для передачи через query
|
||||||
|
*/
|
||||||
|
get = (url, query) => {
|
||||||
|
return this.request(GET, makeUrlWithQuery(url, query));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url - адрес запроса
|
||||||
|
* @param {Object} body - тело запроса
|
||||||
|
*/
|
||||||
|
post = (url, body) => {
|
||||||
|
return this.request(POST, url, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url - адрес запроса
|
||||||
|
* @param {Object} body - тело запроса
|
||||||
|
*/
|
||||||
|
put = (url, body) => {
|
||||||
|
return this.request(PUT, url, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url - адрес запроса
|
||||||
|
* @param {Object<string, string>} query - параметры для передачи через query
|
||||||
|
*/
|
||||||
|
delete = (url, query) => {
|
||||||
|
return this.request(DELETE, makeUrlWithQuery(url, query));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url - адрес запроса
|
||||||
|
* @param {Object<string, string>} query - параметры для передачи через query
|
||||||
|
*/
|
||||||
|
head = (url, query) => {
|
||||||
|
return this.request(HEAD, makeUrlWithQuery(url, query));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url - адрес запроса
|
||||||
|
* @param {Object<string, string>} query - параметры для передачи через query
|
||||||
|
*/
|
||||||
|
options = (url, query) => {
|
||||||
|
return this.request(OPTIONS, makeUrlWithQuery(url, query));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url - адрес запроса
|
||||||
|
* @param {Object} body - тело запроса
|
||||||
|
*/
|
||||||
|
path = (url, body) => {
|
||||||
|
return this.request(PATH, url, body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const http = new HttpApi();
|
||||||
|
|
||||||
|
export default http;
|
||||||
109
src/api/HttpAuthAPI.js
Normal file
109
src/api/HttpAuthAPI.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import authServiceApi from './AuthServiceAPI';
|
||||||
|
import tokenApi from './TokenAPI';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface RequestConfig
|
||||||
|
* @type {Object}
|
||||||
|
* @property {MethodType} method - метод запроса
|
||||||
|
* @property {string} url - адрес запроса
|
||||||
|
* @property {Object} body - тело запроса
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Api прикрепляет ко всем запросом заголовок с токеном авторизации. В случае, когда сервер
|
||||||
|
* @class HttpAuthApi
|
||||||
|
*/
|
||||||
|
class HttpAuthApi {
|
||||||
|
constructor () {
|
||||||
|
this.pendingRequests = [];
|
||||||
|
this.isPendingRefresh = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выполняет какое-то действие над всеми запросами из очереди, пока она не закончится
|
||||||
|
* @param {Function} handler - функция обработчик запроса
|
||||||
|
*/
|
||||||
|
processPendingRequests = (handler) => {
|
||||||
|
this.isPendingRefresh = false;
|
||||||
|
while (this.pendingRequests.length) {
|
||||||
|
const pendingRequest = this.pendingRequests.shift();
|
||||||
|
handler(pendingRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Повторяет все запросы из очереди в порядке их поступления
|
||||||
|
*/
|
||||||
|
repeatRequests = () => {
|
||||||
|
this.processPendingRequests(({resolve, reject, requestConfig}) => {
|
||||||
|
this.request(requestConfig)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выбрасывает ошибки для всех запросов из очереди
|
||||||
|
* @param {axios.error} error - ошибка, которую возвращает axios
|
||||||
|
*/
|
||||||
|
rejectRequests = (error) => {
|
||||||
|
this.processPendingRequests(({reject}) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Перехватывает ошибку 401 (Не авторизован), другие ошибки выбрасывает дальше
|
||||||
|
* @param {axios.error} error - ошибка, которую возвращает axios
|
||||||
|
* @param {Function} resolve - функция промиса, которая разрешает его успешно
|
||||||
|
* @param {Function} reject - функция промиса, которая завершает его неудачей
|
||||||
|
* @param {RequestConfig} requestConfig - настройки для выполнения запроса
|
||||||
|
*/
|
||||||
|
errorHandling = (error, resolve, reject, requestConfig) => {
|
||||||
|
if (error.response.status === 401) {
|
||||||
|
this.isPendingRefresh = true;
|
||||||
|
this.pendingRequests({resolve, reject, requestConfig});
|
||||||
|
authServiceApi.refresh()
|
||||||
|
.then(() => {
|
||||||
|
this.repeatRequests();
|
||||||
|
})
|
||||||
|
.catch((refreshError) => {
|
||||||
|
this.rejectRequests(refreshError);
|
||||||
|
/*
|
||||||
|
* Снять авторизацию и сделать роут
|
||||||
|
* Сбросить все запросы с ошибкой авторизации
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Добавляет к запросу токен в качестве заголовка
|
||||||
|
* @param {RequestConfig} requestConfig - настройки для выполнения запроса
|
||||||
|
*/
|
||||||
|
addTokenToRequest = (requestConfig) => ({
|
||||||
|
...requestConfig,
|
||||||
|
headers: {
|
||||||
|
'Authorization': tokenApi.getAccessToken(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Метод для внешнего использования, выполняет запрос к серверу, используя axios
|
||||||
|
* @param {RequestConfig} requestConfig - настройки для выполнения запроса
|
||||||
|
*/
|
||||||
|
request = (requestConfig) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
axios(this.addTokenToRequest(requestConfig))
|
||||||
|
.then((response) => resolve(response))
|
||||||
|
.catch((error) => this.errorHandling(error, resolve, reject, requestConfig));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const httpAuthApi = new HttpAuthApi();
|
||||||
|
|
||||||
|
export default httpAuthApi;
|
||||||
@ -1,11 +1,15 @@
|
|||||||
|
import {LOCAL_STORAGE_TYPE} from './consts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Класс работы с Local Storage браузера
|
* Класс работы с Local Storage браузера
|
||||||
* @class LocalStorageAPI
|
* @class LocalStorageAPI
|
||||||
* @param {string} key - ключ по которому будет хранится информация
|
* @param {string} key - уникальный ключ для local или session storage
|
||||||
|
* @param {'LOCAL' | 'SESSION'} type - тип storage
|
||||||
*/
|
*/
|
||||||
class LocalStorageAPI {
|
class LocalStorageAPI {
|
||||||
constructor (key) {
|
constructor (key, type = LOCAL_STORAGE_TYPE.LOCAL) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
|
this.api = type === LOCAL_STORAGE_TYPE.LOCAL ? localStorage : sessionStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,7 +22,7 @@ class LocalStorageAPI {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Записывает данные в Local Storage по ключу из конструктора
|
* Записывает данные в Local Storage по ключу из конструктора
|
||||||
* @param {*} value - значение в Local Storage
|
* @param {Object} value - значение в Local Storage
|
||||||
*/
|
*/
|
||||||
createOrUpdate (value) {
|
createOrUpdate (value) {
|
||||||
localStorage.setItem(this.key, JSON.stringify(value));
|
localStorage.setItem(this.key, JSON.stringify(value));
|
||||||
|
|||||||
56
src/api/TokenAPI.js
Normal file
56
src/api/TokenAPI.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import LocalStorageAPI from './LocalStorageAPI';
|
||||||
|
|
||||||
|
const API_NAME = 'storageServiceUITokenApi';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface TokenPair
|
||||||
|
* @type {Object}
|
||||||
|
* @property {string} access_token - токен авторизации
|
||||||
|
* @property {string} refresh_token = токен рефреша авторизации
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Api сохраняет и возвращает токены авторизации
|
||||||
|
* @class TokenApi
|
||||||
|
*/
|
||||||
|
class TokenApi {
|
||||||
|
constructor () {
|
||||||
|
this.localApi = new LocalStorageAPI(API_NAME);
|
||||||
|
this.sessionApi = new LocalStorageAPI(API_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сохраняет пару токенов
|
||||||
|
* @param {TokenPair} tokens - пара токенов
|
||||||
|
*/
|
||||||
|
saveTokenPair = (tokens) => {
|
||||||
|
this.localApi.createOrUpdate(tokens.refresh_token);
|
||||||
|
this.sessionApi.createOrUpdate(tokens.access_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Возвращает токен авторизации
|
||||||
|
*/
|
||||||
|
getAccessToken = () => {
|
||||||
|
return this.localApi.request();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Возвращает токен ревреша авторизации
|
||||||
|
*/
|
||||||
|
getRefreshToken = () => {
|
||||||
|
return this.sessionApi.request();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очищает токены из памяти
|
||||||
|
*/
|
||||||
|
clearTokents = () => {
|
||||||
|
this.localApi.remove();
|
||||||
|
this.sessionApi.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenApi = new TokenApi();
|
||||||
|
|
||||||
|
export default tokenApi;
|
||||||
@ -34,3 +34,15 @@ export const ENDPOINTS = {
|
|||||||
export const TESTING_HEADERS = {
|
export const TESTING_HEADERS = {
|
||||||
'Api-Name': 'store-service-test',
|
'Api-Name': 'store-service-test',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const AUTH_SERVICE = 'http://api.auth.vigdorov.ru';
|
||||||
|
|
||||||
|
export const AUTH_ENDPOINTS = {
|
||||||
|
AUTH: '/auth',
|
||||||
|
REFRESH: '/refresh',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LOCAL_STORAGE_TYPE = {
|
||||||
|
LOCAL: 'LOCAL',
|
||||||
|
SESSION: 'SESSION',
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user