HM-83. Добавлены классы для работы с авторизацией (#36)

This commit is contained in:
Nikolay
2020-07-30 08:32:56 +03:00
committed by GitHub
parent a9282a38e5
commit d957d2d208
6 changed files with 321 additions and 3 deletions

45
src/api/AuthServiceAPI.js Normal file
View 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
View 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
View 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;

View File

@ -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
View 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;

View File

@ -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',
};