Merge "KeystoneApiService error handling"
This commit is contained in:
commit
24facd7a39
|
@ -16,11 +16,10 @@
|
|||
|
||||
import { Map, fromJS } from 'immutable';
|
||||
|
||||
import KeystoneApiErrorHandler from '../services/KeystoneApiErrorHandler';
|
||||
import KeystoneApiService from '../services/KeystoneApiService';
|
||||
import LoginConstants from '../constants/LoginConstants';
|
||||
import ZaqarWebSocketService from '../services/ZaqarWebSocketService';
|
||||
import logger from '../services/logging/LoggingService';
|
||||
import ZaqarWebSocketService from '../services/ZaqarWebSocketService';
|
||||
import cookie from 'react-cookie';
|
||||
|
||||
export default {
|
||||
|
@ -28,19 +27,28 @@ export default {
|
|||
return (dispatch, getState) => {
|
||||
dispatch(this.userAuthStarted());
|
||||
KeystoneApiService.authenticateUserViaToken(keystoneAuthTokenId)
|
||||
.then(result => {
|
||||
const tokenId = result.request.getResponseHeader('X-Subject-Token');
|
||||
.then(response => {
|
||||
const {
|
||||
data: { token },
|
||||
headers: { 'x-subject-token': tokenId }
|
||||
} = response;
|
||||
cookie.save('keystoneAuthTokenId', tokenId, { path: '/' });
|
||||
dispatch(this.userAuthSuccess(tokenId, result.response.token));
|
||||
dispatch(this.userAuthSuccess(tokenId, token));
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error(
|
||||
'Error in LoginActions.authenticateUserViaToken',
|
||||
error.stack || error
|
||||
dispatch(
|
||||
this.userAuthFailure([
|
||||
{
|
||||
title: 'Unauthorized',
|
||||
message: error.message
|
||||
}
|
||||
])
|
||||
);
|
||||
logger.error(
|
||||
'Could not authenticate user via token',
|
||||
error,
|
||||
error.stack
|
||||
);
|
||||
let errorHandler = new KeystoneApiErrorHandler(error);
|
||||
cookie.remove('keystoneAuthTokenId');
|
||||
dispatch(this.userAuthFailure(errorHandler.errors));
|
||||
});
|
||||
};
|
||||
},
|
||||
|
@ -49,23 +57,24 @@ export default {
|
|||
return (dispatch, getState) => {
|
||||
dispatch(this.userAuthStarted());
|
||||
KeystoneApiService.authenticateUser(formData.username, formData.password)
|
||||
.then(result => {
|
||||
const tokenId = result.request.getResponseHeader('X-Subject-Token');
|
||||
.then(response => {
|
||||
const {
|
||||
data: { token },
|
||||
headers: { 'x-subject-token': tokenId }
|
||||
} = response;
|
||||
cookie.save('keystoneAuthTokenId', tokenId, { path: '/' });
|
||||
dispatch(this.userAuthSuccess(tokenId, result.response.token));
|
||||
dispatch(this.userAuthSuccess(tokenId, token));
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error(
|
||||
'Error in LoginActions.authenticateUser',
|
||||
error.stack || error
|
||||
);
|
||||
let errorHandler = new KeystoneApiErrorHandler(error, formFields);
|
||||
dispatch(
|
||||
this.userAuthFailure(
|
||||
errorHandler.errors,
|
||||
errorHandler.formFieldErrors
|
||||
)
|
||||
this.userAuthFailure([
|
||||
{
|
||||
title: 'Unauthorized',
|
||||
message: error.message
|
||||
}
|
||||
])
|
||||
);
|
||||
logger.error('Could not authenticate user', error, error.stack);
|
||||
});
|
||||
};
|
||||
},
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
/**
|
||||
* Copyright 2017 Red Hat Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License. You may obtain
|
||||
* a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @classdesc Implements base for API and Form error handling
|
||||
*/
|
||||
export default class BaseHttpRequestErrorHandler {
|
||||
constructor(xmlHttpRequestError, formInputFieldNames) {
|
||||
this.xmlHttpRequestError = xmlHttpRequestError;
|
||||
this.formInputFieldNames = formInputFieldNames || [];
|
||||
this._errors = this._generateErrors(this.xmlHttpRequestError);
|
||||
this._formFieldErrors = this._generateFormFieldErrors(
|
||||
this.xmlHttpRequestError,
|
||||
this.formInputFieldNames
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
Generates errors
|
||||
@param {object} xmlHttpRequestError - The Error object
|
||||
@returns {array} array of error objects with type, title and message properties
|
||||
*/
|
||||
_generateErrors(xmlHttpRequestError) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
Generates form field frrors
|
||||
@param {object} xmlHttpRequestError - The Error object
|
||||
@param {array} formInputFieldNames - array of strings with form field names
|
||||
@returns {object} object with with form field names as keys and error messages as
|
||||
values. e.g. {'username': 'Username does not exist'}
|
||||
*/
|
||||
_generateFormFieldErrors(xmlHttpRequestError, formInputFieldNames) {
|
||||
return {};
|
||||
}
|
||||
|
||||
get errors() {
|
||||
return this._errors;
|
||||
}
|
||||
|
||||
get formFieldErrors() {
|
||||
return this._formFieldErrors;
|
||||
}
|
||||
}
|
|
@ -16,7 +16,5 @@
|
|||
|
||||
import { getAppConfig } from '../services/utils';
|
||||
|
||||
let HOST = location.protocol + '//' + location.hostname;
|
||||
let KEYSTONE_URL = getAppConfig().keystone || HOST + ':5000/v3';
|
||||
|
||||
export const AUTH_URL = KEYSTONE_URL + '/auth/tokens';
|
||||
const host = location.protocol + '//' + location.hostname;
|
||||
export const KEYSTONE_URL = getAppConfig().keystone || host + ':5000/v3';
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
/**
|
||||
* Copyright 2017 Red Hat Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License. You may obtain
|
||||
* a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import BaseHttpRequestErrorHandler
|
||||
from '../components/utils/BaseHttpRequestErrorHandler';
|
||||
import LoginActions from '../actions/LoginActions';
|
||||
import store from '../store';
|
||||
|
||||
export default class KeystoneApiErrorHandler
|
||||
extends BaseHttpRequestErrorHandler {
|
||||
_generateErrors(errorObj) {
|
||||
let errors = [];
|
||||
// A weak check to find out if it's not an xhr object.
|
||||
if (!errorObj.status && errorObj.message) {
|
||||
errors.push({
|
||||
title: 'Error',
|
||||
message: errorObj.message
|
||||
});
|
||||
return errors;
|
||||
}
|
||||
switch (errorObj.status) {
|
||||
case 0:
|
||||
errors.push({
|
||||
title: 'Connection Error',
|
||||
message: 'Connection to Keystone is not available'
|
||||
});
|
||||
break;
|
||||
case 401: {
|
||||
let error = JSON.parse(errorObj.responseText).error;
|
||||
errors.push({
|
||||
title: 'Unauthorized',
|
||||
message: error.message
|
||||
});
|
||||
store.dispatch(LoginActions.logoutUser());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
// TODO(jtomasek): remove this, I am leaving this here just for example reasons
|
||||
// this function should be implemented by form related subclass.
|
||||
_generateFormFieldErrors() {
|
||||
return {};
|
||||
}
|
||||
}
|
|
@ -14,30 +14,33 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import request from 'reqwest';
|
||||
import axios from 'axios';
|
||||
import when from 'when';
|
||||
|
||||
import { AUTH_URL } from '../constants/KeystoneApiConstants';
|
||||
import {
|
||||
AuthenticationError,
|
||||
KeystoneApiError,
|
||||
ConnectionError
|
||||
} from './errors';
|
||||
import { KEYSTONE_URL } from '../constants/KeystoneApiConstants';
|
||||
|
||||
class KeystoneApiService {
|
||||
defaultRequest(additionalAttributes) {
|
||||
return _.merge(
|
||||
return axios(
|
||||
Object.assign(
|
||||
{
|
||||
url: AUTH_URL,
|
||||
method: 'POST',
|
||||
crossOrigin: true,
|
||||
contentType: 'application/json',
|
||||
type: 'json'
|
||||
baseURL: KEYSTONE_URL,
|
||||
url: '/auth/tokens',
|
||||
method: 'POST'
|
||||
},
|
||||
additionalAttributes
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
authenticateUser(username, password) {
|
||||
let req = request(
|
||||
this.defaultRequest({
|
||||
data: JSON.stringify({
|
||||
return this.defaultRequest({
|
||||
data: {
|
||||
auth: {
|
||||
identity: {
|
||||
methods: ['password'],
|
||||
|
@ -60,24 +63,13 @@ class KeystoneApiService {
|
|||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
// We're passing the req object to the next handler in the chain so that we
|
||||
// can inspect response headers later.
|
||||
return when(req, response => {
|
||||
return {
|
||||
request: req.request,
|
||||
response
|
||||
};
|
||||
});
|
||||
}
|
||||
}).catch(handleErrors);
|
||||
}
|
||||
|
||||
authenticateUserViaToken(keystoneAuthTokenId) {
|
||||
let req = request(
|
||||
this.defaultRequest({
|
||||
data: JSON.stringify({
|
||||
return this.defaultRequest({
|
||||
data: {
|
||||
auth: {
|
||||
identity: {
|
||||
methods: ['token'],
|
||||
|
@ -94,19 +86,26 @@ class KeystoneApiService {
|
|||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
// We're passing the req object to the next handler in the chain so that we
|
||||
// can inspect response headers later.
|
||||
return when(req, response => {
|
||||
return {
|
||||
request: req.request,
|
||||
response
|
||||
};
|
||||
});
|
||||
}
|
||||
}).catch(handleErrors);
|
||||
}
|
||||
}
|
||||
|
||||
const handleErrors = e => {
|
||||
if (e.response && e.response.status === 401) {
|
||||
return when.reject(new AuthenticationError(e));
|
||||
} else if (e.response) {
|
||||
return when.reject(new KeystoneApiError(e));
|
||||
} else if (e.request) {
|
||||
return when.reject(
|
||||
new ConnectionError(
|
||||
'Connection to Keystone API could not be established',
|
||||
e
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return when.reject(e);
|
||||
}
|
||||
};
|
||||
|
||||
export default new KeystoneApiService();
|
||||
|
|
|
@ -27,7 +27,7 @@ class BaseAxiosError extends ExtendableError {
|
|||
|
||||
class AuthenticationError extends BaseAxiosError {
|
||||
constructor(e) {
|
||||
super('Authentication failed, please log in', e);
|
||||
super('Authentication failed, please log in again', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,6 +72,12 @@ class HeatApiError extends BaseAxiosError {
|
|||
}
|
||||
}
|
||||
|
||||
class KeystoneApiError extends BaseAxiosError {
|
||||
constructor(e) {
|
||||
super(e.response.data.error.message, e);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
BaseAxiosError,
|
||||
AuthenticationError,
|
||||
|
@ -81,5 +87,6 @@ export {
|
|||
SwiftApiError,
|
||||
IronicApiError,
|
||||
IronicInspectorApiError,
|
||||
HeatApiError
|
||||
HeatApiError,
|
||||
KeystoneApiError
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue