diff --git a/frontend/components/forms/LoginForm/LoginForm.jsx b/frontend/components/forms/LoginForm/LoginForm.jsx index 059f520558..0c21b90da1 100644 --- a/frontend/components/forms/LoginForm/LoginForm.jsx +++ b/frontend/components/forms/LoginForm/LoginForm.jsx @@ -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 (
Avatar { + 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 ( + + ); + } + + render () { + const { showLoginForm } = this; return (
@@ -62,7 +94,7 @@ export class LoginPage extends Component { transitionEnterTimeout={500} transitionLeaveTimeout={300} > - {loginVisible && } + {showLoginForm()}
); diff --git a/frontend/redux/nodes/auth/actions.js b/frontend/redux/nodes/auth/actions.js index 11cfcbe773..6ce7321895 100644 --- a/frontend/redux/nodes/auth/actions.js +++ b/frontend/redux/nodes/auth/actions.js @@ -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 { diff --git a/frontend/redux/nodes/auth/reducer.js b/frontend/redux/nodes/auth/reducer.js index ebbaeaa497..f924569cb1 100644 --- a/frontend/redux/nodes/auth/reducer.js +++ b/frontend/redux/nodes/auth/reducer.js @@ -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 { diff --git a/server/service/endpoint_middleware.go b/server/service/endpoint_middleware.go index 8ffed7d1c4..529979ccee 100644 --- a/server/service/endpoint_middleware.go +++ b/server/service/endpoint_middleware.go @@ -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 } diff --git a/server/service/http_auth.go b/server/service/http_auth.go index 46a91d3645..c10a89f60d 100644 --- a/server/service/http_auth.go +++ b/server/service/http_auth.go @@ -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 diff --git a/server/service/service_sessions.go b/server/service/service_sessions.go index 3e38e8e4e0..aa0c02caad 100644 --- a/server/service/service_sessions.go +++ b/server/service/service_sessions.go @@ -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 {