mirror of
https://github.com/fleetdm/fleet
synced 2026-05-22 08:28:52 +00:00
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:
parent
24b9baec1f
commit
738d7253c2
7 changed files with 75 additions and 20 deletions
|
|
@ -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')}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue