diff --git a/http/authentication_middleware.go b/http/authentication_middleware.go index 083e0c6481..940ac84671 100644 --- a/http/authentication_middleware.go +++ b/http/authentication_middleware.go @@ -72,7 +72,7 @@ func (h *AuthenticationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request ctx := r.Context() scheme, err := ProbeAuthScheme(r) if err != nil { - ForbiddenError(ctx, err, w) + UnauthorizedError(ctx, w) return } @@ -95,7 +95,7 @@ func (h *AuthenticationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request return } - ForbiddenError(ctx, fmt.Errorf("unauthorized"), w) + UnauthorizedError(ctx, w) } func (h *AuthenticationHandler) extractAuthorization(ctx context.Context, r *http.Request) (context.Context, error) { diff --git a/http/authentication_test.go b/http/authentication_test.go index 43be11c184..90e08e4e04 100644 --- a/http/authentication_test.go +++ b/http/authentication_test.go @@ -66,7 +66,7 @@ func TestAuthenticationHandler(t *testing.T) { session: "abc123", }, wants: wants{ - code: http.StatusForbidden, + code: http.StatusUnauthorized, }, }, { @@ -100,7 +100,7 @@ func TestAuthenticationHandler(t *testing.T) { token: "abc123", }, wants: wants{ - code: http.StatusForbidden, + code: http.StatusUnauthorized, }, }, { @@ -111,7 +111,7 @@ func TestAuthenticationHandler(t *testing.T) { }, args: args{}, wants: wants{ - code: http.StatusForbidden, + code: http.StatusUnauthorized, }, }, } @@ -264,7 +264,7 @@ func TestAuthenticationHandler_NoAuthRoutes(t *testing.T) { path: "/api/v2/write", }, wants: wants{ - code: http.StatusForbidden, + code: http.StatusUnauthorized, }, }, } diff --git a/http/errors.go b/http/errors.go index 1e501bb290..8c2dc35ee8 100644 --- a/http/errors.go +++ b/http/errors.go @@ -164,6 +164,14 @@ func ForbiddenError(ctx context.Context, err error, w http.ResponseWriter) { }, w) } +// UnauthorizedError encodes a error message and status code for unauthorized access. +func UnauthorizedError(ctx context.Context, w http.ResponseWriter) { + EncodeError(ctx, &platform.Error{ + Code: platform.EUnauthorized, + Msg: "unauthorized access", + }, w) +} + // statusCode returns the http status code for an error. func statusCode(e kerrors.Error) int { if e.Code > 0 { @@ -194,6 +202,6 @@ var statusCodePlatformError = map[string]int{ platform.ENotFound: http.StatusNotFound, platform.EUnavailable: http.StatusServiceUnavailable, platform.EForbidden: http.StatusForbidden, - platform.EUnauthorized: http.StatusForbidden, + platform.EUnauthorized: http.StatusUnauthorized, platform.EMethodNotAllowed: http.StatusMethodNotAllowed, } diff --git a/http/session_handler.go b/http/session_handler.go index 32a981c72b..a671385a39 100644 --- a/http/session_handler.go +++ b/http/session_handler.go @@ -57,20 +57,19 @@ func (h *SessionHandler) handleSignin(w http.ResponseWriter, r *http.Request) { req, err := decodeSigninRequest(ctx, r) if err != nil { - h.Logger.Info("failed to decode request", zap.Error(err)) - EncodeError(ctx, err, w) + UnauthorizedError(ctx, w) return } if err := h.BasicAuthService.ComparePassword(ctx, req.Username, req.Password); err != nil { // Don't log here, it should already be handled by the service - EncodeError(ctx, err, w) + UnauthorizedError(ctx, w) return } s, e := h.SessionService.CreateSession(ctx, req.Username) if e != nil { - EncodeError(ctx, err, w) + UnauthorizedError(ctx, w) return } @@ -104,13 +103,12 @@ func (h *SessionHandler) handleSignout(w http.ResponseWriter, r *http.Request) { req, err := decodeSignoutRequest(ctx, r) if err != nil { - h.Logger.Info("failed to decode request", zap.Error(err)) - EncodeError(ctx, err, w) + UnauthorizedError(ctx, w) return } if err := h.SessionService.ExpireSession(ctx, req.Key); err != nil { - EncodeError(ctx, err, w) + UnauthorizedError(ctx, w) return } diff --git a/http/swagger.yml b/http/swagger.yml index a5ba9c0e5b..e13c6205bc 100644 --- a/http/swagger.yml +++ b/http/swagger.yml @@ -15,6 +15,12 @@ paths: responses: '204': description: succesfully authenticated + '401': + description: unauthorized access + content: + application/json: + schema: + $ref: "#/components/schemas/Error" default: description: unsuccessful authentication content: @@ -29,6 +35,12 @@ paths: responses: '204': description: session successfully expired + '401': + description: unauthorized access + content: + application/json: + schema: + $ref: "#/components/schemas/Error" default: description: unsuccessful session exipry content: diff --git a/ui/src/Signin.tsx b/ui/src/Signin.tsx index d4345797ee..19ccb443cc 100644 --- a/ui/src/Signin.tsx +++ b/ui/src/Signin.tsx @@ -1,11 +1,18 @@ // Libraries import React, {ReactElement, PureComponent} from 'react' import {withRouter, WithRouterProps} from 'react-router' +import {connect} from 'react-redux' // Components import {ErrorHandling} from 'src/shared/decorators/errors' import {getMe} from 'src/shared/apis/v2/user' +// Actions +import {notify as notifyAction} from 'src/shared/actions/notifications' + +// Constants +import {sessionTimedOut} from 'src/shared/copy/notifications' + // Types import {RemoteDataState} from 'src/types' @@ -17,7 +24,11 @@ interface OwnProps { children: ReactElement } -type Props = OwnProps & WithRouterProps +interface DispatchProps { + notify: typeof notifyAction +} + +type Props = OwnProps & WithRouterProps & DispatchProps const FETCH_WAIT = 60000 @@ -75,6 +86,7 @@ export class Signin extends PureComponent { if (pathname !== '/') { returnTo = `?returnTo=${pathname}` + this.props.notify(sessionTimedOut()) } this.props.router.push(`/signin${returnTo}`) @@ -82,4 +94,11 @@ export class Signin extends PureComponent { } } -export default withRouter(Signin) +const mdtp: DispatchProps = { + notify: notifyAction, +} + +export default connect( + null, + mdtp +)(withRouter(Signin)) diff --git a/ui/src/onboarding/components/SigninForm.tsx b/ui/src/onboarding/components/SigninForm.tsx index e134302e25..632902a4de 100644 --- a/ui/src/onboarding/components/SigninForm.tsx +++ b/ui/src/onboarding/components/SigninForm.tsx @@ -110,7 +110,15 @@ class SigninForm extends PureComponent { await signin({username, password}) this.handleRedirect() } catch (error) { - const message = get(error, 'data.msg', '') + const message = get(error, 'response.data.msg', '') + const status = get(error, 'response.status', '') + + if (status === 401) { + return notify({ + ...copy.SigninError, + message: 'Login failed: username or password is invalid', + }) + } if (!message) { return notify(copy.SigninError)