From 7e9c7ce60a90f3628888f178c27642561643abaa Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Fri, 1 Apr 2016 11:48:19 -0400 Subject: Visiting invalid URLs and bad APIs causes redirect to error page --- api/api.go | 2 ++ api/context.go | 36 ++++++++++++------------- i18n/en.json | 24 +++++++++++++++++ templates/error.html | 24 ----------------- webapp/components/error_page.jsx | 58 ++++++++++++++++++++++++++++++++++++++++ webapp/root.jsx | 25 ++++++++++++++++- 6 files changed, 125 insertions(+), 44 deletions(-) delete mode 100644 templates/error.html create mode 100644 webapp/components/error_page.jsx diff --git a/api/api.go b/api/api.go index 20f77e558..476047877 100644 --- a/api/api.go +++ b/api/api.go @@ -27,6 +27,8 @@ func InitApi() { InitWebhook(r) InitPreference(r) InitLicense(r) + // 404 on any api route before web.go has a chance to serve it + Srv.Router.Handle("/api/{anything:.*}", http.HandlerFunc(Handle404)) utils.InitHTML() } diff --git a/api/context.go b/api/context.go index eed035daf..0f7ba0fff 100644 --- a/api/context.go +++ b/api/context.go @@ -476,25 +476,23 @@ func IsPrivateIpAddress(ipAddress string) bool { } func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) { - T, locale := utils.GetTranslationsAndLocale(w, r) - page := utils.NewHTMLTemplate("error", locale) - page.Props["Message"] = err.Message - page.Props["Details"] = err.DetailedError - - pathParts := strings.Split(r.URL.Path, "/") - if len(pathParts) > 1 { - page.Props["SiteURL"] = GetProtocol(r) + "://" + r.Host + "/" + pathParts[1] - } else { - page.Props["SiteURL"] = GetProtocol(r) + "://" + r.Host - } - - page.Props["Title"] = T("api.templates.error.title", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]}) - page.Props["Link"] = T("api.templates.error.link") - - w.WriteHeader(err.StatusCode) - if rErr := page.RenderToWriter(w); rErr != nil { - l4g.Error("Failed to create error page: " + rErr.Error() + ", Original error: " + err.Error()) - } + T, _ := utils.GetTranslationsAndLocale(w, r) + + title := T("api.templates.error.title", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]}) + message := err.Message + details := err.DetailedError + link := "/" + linkMessage := T("api.templates.error.link") + + http.Redirect( + w, + r, + "/error?title="+url.QueryEscape(title)+ + "&message="+url.QueryEscape(message)+ + "&details="+url.QueryEscape(details)+ + "&link="+url.QueryEscape(link)+ + "&linkmessage="+url.QueryEscape(linkMessage), + http.StatusTemporaryRedirect) } func Handle404(w http.ResponseWriter, r *http.Request) { diff --git a/i18n/en.json b/i18n/en.json index 7dcc351f1..41889ab3e 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -3734,5 +3734,29 @@ { "id": "web.watcher_fail.error", "translation": "Failed to add directory to watcher %v" + }, + { + "id": "error.not_found.title", + "translation": "Page not found" + }, + { + "id": "error.not_found.message", + "translation": "The page you where trying to reach does not exist." + }, + { + "id": "error.not_found.link_message", + "translation": "Back to Mattermost" + }, + { + "id": "error.generic.title", + "translation": "Error" + }, + { + "id": "error.generic.message", + "translation": "An error has occoured." + }, + { + "id": "error.generic.link_message", + "translation": "Back to Mattermost" } ] diff --git a/templates/error.html b/templates/error.html deleted file mode 100644 index 5aa48098f..000000000 --- a/templates/error.html +++ /dev/null @@ -1,24 +0,0 @@ -{{define "error"}} - - -{{template "head" . }} - -
-
-
- -
-

{{.Props.Title}}

-

{{ .Props.Message }}

- {{.Props.Link}} -
-
- - - -{{end}} diff --git a/webapp/components/error_page.jsx b/webapp/components/error_page.jsx new file mode 100644 index 000000000..53f0fce82 --- /dev/null +++ b/webapp/components/error_page.jsx @@ -0,0 +1,58 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; + +import React from 'react'; +import {Link} from 'react-router'; + +import * as Utils from 'utils/utils.jsx'; + +export default class ErrorPage extends React.Component { + componentDidMount() { + $('body').attr('class', 'sticky error'); + } + componentWillUnmount() { + $('body').attr('class', ''); + } + render() { + let title = this.props.location.query.title; + if (!title || title === '') { + title = Utils.localizeMessage('error.generic.title', 'Error'); + } + + let message = this.props.location.query.message; + if (!message || message === '') { + message = Utils.localizeMessage('error.generic.message', 'An error has occoured.'); + } + + let link = this.props.location.query.link; + if (!link || link === '') { + link = '/'; + } + + let linkMessage = this.props.location.query.linkmessage; + if (!linkMessage || linkMessage === '') { + linkMessage = Utils.localizeMessage('error.generic.link_message', 'Back to Mattermost'); + } + + return ( +
+
+
+ +
+

{title}

+

{message}

+ {linkMessage} +
+
+ ); + } +} + +ErrorPage.defaultProps = { +}; +ErrorPage.propTypes = { + location: React.PropTypes.object +}; diff --git a/webapp/root.jsx b/webapp/root.jsx index 9c2708506..da5980c33 100644 --- a/webapp/root.jsx +++ b/webapp/root.jsx @@ -10,7 +10,7 @@ import 'sass/styles.scss'; import React from 'react'; import ReactDOM from 'react-dom'; -import {Router, Route, IndexRoute, IndexRedirect, browserHistory} from 'react-router'; +import {Router, Route, IndexRoute, IndexRedirect, Redirect, browserHistory} from 'react-router'; import Root from 'components/root.jsx'; import LoggedIn from 'components/logged_in.jsx'; import NotLoggedIn from 'components/not_logged_in.jsx'; @@ -28,6 +28,7 @@ import BrowserStore from 'stores/browser_store.jsx'; import SignupTeam from 'components/signup_team.jsx'; import * as Client from 'utils/client.jsx'; import * as Websockets from 'action_creators/websocket_actions.jsx'; +import * as Utils from 'utils/utils.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; import SignupTeamConfirm from 'components/signup_team_confirm.jsx'; import SignupUserComplete from 'components/signup_user_complete.jsx'; @@ -41,6 +42,7 @@ import InstalledIntegrations from 'components/backstage/installed_integrations.j import AddIntegration from 'components/backstage/add_integration.jsx'; import AddIncomingWebhook from 'components/backstage/add_incoming_webhook.jsx'; import AddOutgoingWebhook from 'components/backstage/add_outgoing_webhook.jsx'; +import ErrorPage from 'components/error_page.jsx'; import SignupTeamComplete from 'components/signup_team_complete/components/signup_team_complete.jsx'; import WelcomePage from 'components/signup_team_complete/components/team_signup_welcome_page.jsx'; @@ -61,6 +63,13 @@ import Login from 'components/login/login.jsx'; import * as I18n from 'i18n/i18n.jsx'; +const notFoundParams = { + title: Utils.localizeMessage('error.not_found.title', 'Page not found'), + message: Utils.localizeMessage('error.not_found.message', 'The page you where trying to reach does not exist'), + link: '/', + linkmessage: Utils.localizeMessage('error.not_found.link_message', 'Back to Mattermost') +}; + // This is for anything that needs to be done for ALL react components. // This runs before we start to render anything. function preRenderSetup(callwhendone) { @@ -218,6 +227,10 @@ function renderRootComponent() { component={Root} onEnter={onRootEnter} > + + + -- cgit v1.2.3-1-g7c22