Login form displays error message (#243)

* Login form displays error message

* default bad auth to a generic error for the client
This commit is contained in:
Mike Stone 2016-09-28 20:46:45 -04:00 committed by GitHub
parent 24b9baec1f
commit 738d7253c2
7 changed files with 75 additions and 20 deletions

View file

@ -1,5 +1,6 @@
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import { noop } from 'lodash';
import radium from 'radium';
import avatar from '../../../../assets/images/avatar.svg';
import componentStyles from './styles';
@ -8,12 +9,21 @@ import InputFieldWithIcon from '../fields/InputFieldWithIcon';
import paths from '../../../router/paths';
import validatePresence from '../validators/validate_presence';
class LoginForm extends Component {
static propTypes = {
serverErrors: PropTypes.shape({
username: PropTypes.string,
password: PropTypes.string,
}),
onChange: PropTypes.func,
onSubmit: PropTypes.func,
};
static defaultProps = {
onChange: noop,
serverErrors: {},
};
constructor (props) {
super(props);
@ -32,8 +42,11 @@ class LoginForm extends Component {
onInputChange = (formField) => {
return ({ target }) => {
const { errors, formData } = this.state;
const { onChange } = this.props;
const { value } = target;
onChange(value);
this.setState({
errors: {
...errors,
@ -131,22 +144,24 @@ class LoginForm extends Component {
formStyles,
submitButtonStyles,
} = componentStyles;
const { serverErrors } = this.props;
const { errors } = this.state;
const { onInputChange, onFormSubmit } = this;
return (
<form onSubmit={onFormSubmit} style={formStyles}>
<div style={containerStyles}>
<img alt="Avatar" src={avatar} />
<InputFieldWithIcon
autofocus
error={errors.username}
error={errors.username || serverErrors.username}
iconName="kolidecon-username"
name="username"
onChange={onInputChange('username')}
placeholder="Username or Email"
/>
<InputFieldWithIcon
error={errors.password}
error={errors.password || serverErrors.password}
iconName="kolidecon-password"
name="password"
onChange={onInputChange('password')}

View file

@ -2,15 +2,14 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import { clearAuthErrors, loginUser } from '../../redux/nodes/auth/actions';
import debounce from '../../utilities/debounce';
import local from '../../utilities/local';
import LoginForm from '../../components/forms/LoginForm';
import LoginSuccessfulPage from '../LoginSuccessfulPage';
import { loginUser } from '../../redux/nodes/auth/actions';
import paths from '../../router/paths';
import './styles.scss';
export class LoginPage extends Component {
static propTypes = {
@ -37,6 +36,14 @@ export class LoginPage extends Component {
return false;
}
onChange = () => {
const { dispatch, error } = this.props;
if (error) return dispatch(clearAuthErrors);
return false;
};
onSubmit = debounce((formData) => {
const { dispatch } = this.props;
const { HOME } = paths;
@ -50,9 +57,34 @@ export class LoginPage extends Component {
});
})
render () {
const { onSubmit } = this;
serverErrors = () => {
const { error } = this.props;
if (!error) return undefined;
return {
username: error,
password: 'password',
};
}
showLoginForm = () => {
const { loginVisible } = this.state;
const { onChange, onSubmit, serverErrors } = this;
if (!loginVisible) return false;
return (
<LoginForm
onChange={onChange}
onSubmit={onSubmit}
serverErrors={serverErrors()}
/>
);
}
render () {
const { showLoginForm } = this;
return (
<div>
@ -62,7 +94,7 @@ export class LoginPage extends Component {
transitionEnterTimeout={500}
transitionLeaveTimeout={300}
>
{loginVisible && <LoginForm onSubmit={onSubmit} visible={loginVisible} />}
{showLoginForm()}
</ReactCSSTransitionGroup>
</div>
);

View file

@ -1,6 +1,7 @@
import md5 from 'js-md5';
import Kolide from '../../../kolide';
export const CLEAR_AUTH_ERRORS = 'CLEAR_AUTH_ERRORS';
export const LOGIN_REQUEST = 'LOGIN_REQUEST';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_FAILURE = 'LOGIN_FAILURE';
@ -8,6 +9,7 @@ export const LOGOUT_REQUEST = 'LOGOUT_REQUEST';
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const LOGOUT_FAILURE = 'LOGOUT_FAILURE';
export const clearAuthErrors = { type: CLEAR_AUTH_ERRORS };
export const loginRequest = { type: LOGIN_REQUEST };
export const loginSuccess = (user) => {
return {

View file

@ -1,4 +1,5 @@
import {
CLEAR_AUTH_ERRORS,
LOGIN_FAILURE,
LOGIN_REQUEST,
LOGIN_SUCCESS,
@ -15,6 +16,11 @@ export const initialState = {
const reducer = (state = initialState, action) => {
switch (action.type) {
case CLEAR_AUTH_ERRORS:
return {
...state,
error: null,
};
case LOGIN_REQUEST:
case LOGOUT_REQUEST:
return {

View file

@ -6,9 +6,9 @@ import (
jwt "github.com/dgrijalva/jwt-go"
"github.com/go-kit/kit/endpoint"
"github.com/kolide/kolide-ose/server/kolide"
"github.com/kolide/kolide-ose/server/contexts/token"
"github.com/kolide/kolide-ose/server/contexts/viewer"
"github.com/kolide/kolide-ose/server/kolide"
"golang.org/x/net/context"
)
@ -43,27 +43,27 @@ func authViewer(ctx context.Context, jwtKey string, bearerToken string, svc koli
return []byte(jwtKey), nil
})
if err != nil {
return nil, authError{reason: err.Error(), clientReason: "bad credentials"}
return nil, authError{reason: err.Error()}
}
claims, ok := jwtToken.Claims.(jwt.MapClaims)
if !ok {
return nil, authError{reason: "no jwt claims", clientReason: "bad credentials"}
return nil, authError{reason: "no jwt claims"}
}
sessionKeyClaim, ok := claims["session_key"]
if !ok {
return nil, authError{reason: "no session_key in JWT claims", clientReason: "bad credentials"}
return nil, authError{reason: "no session_key in JWT claims"}
}
sessionKey, ok := sessionKeyClaim.(string)
if !ok {
return nil, authError{reason: "non-string key in sessionClaim", clientReason: "bad credentials"}
return nil, authError{reason: "non-string key in sessionClaim"}
}
session, err := svc.GetSessionByKey(ctx, sessionKey)
if err != nil {
return nil, authError{reason: err.Error(), clientReason: "bad credentials"}
return nil, authError{reason: err.Error()}
}
user, err := svc.User(ctx, session.UserID)
if err != nil {
return nil, authError{reason: err.Error(), clientReason: "bad credentials"}
return nil, authError{reason: err.Error()}
}
return &viewer.Viewer{User: user, Session: session}, nil
}

View file

@ -6,9 +6,9 @@ import (
"strings"
kithttp "github.com/go-kit/kit/transport/http"
"github.com/kolide/kolide-ose/server/kolide"
"github.com/kolide/kolide-ose/server/contexts/token"
"github.com/kolide/kolide-ose/server/contexts/viewer"
"github.com/kolide/kolide-ose/server/kolide"
"golang.org/x/net/context"
)
@ -29,7 +29,7 @@ func (e authError) AuthError() string {
if e.clientReason != "" {
return e.clientReason
}
return "authentication error"
return "username or email and password do not match"
}
// permissionError, set when user is authenticated, but not allowed to perform action

View file

@ -6,9 +6,9 @@ import (
"strings"
"time"
"github.com/kolide/kolide-ose/server/contexts/viewer"
"github.com/kolide/kolide-ose/server/datastore"
"github.com/kolide/kolide-ose/server/kolide"
"github.com/kolide/kolide-ose/server/contexts/viewer"
"golang.org/x/net/context"
)
@ -17,7 +17,7 @@ func (svc service) Login(ctx context.Context, username, password string) (*kolid
switch err {
case nil:
case datastore.ErrNotFound:
return nil, "", authError{reason: "no such user", clientReason: "bad credentials"}
return nil, "", authError{reason: "no such user"}
default:
return nil, "", err
}
@ -25,7 +25,7 @@ func (svc service) Login(ctx context.Context, username, password string) (*kolid
return nil, "", authError{reason: "account disabled", clientReason: "account disabled"}
}
if err := user.ValidatePassword(password); err != nil {
return nil, "", authError{reason: "bad password", clientReason: "bad credentials"}
return nil, "", authError{reason: "bad password"}
}
token, err := svc.makeSession(user.ID)
if err != nil {