Merge pull request #2614 from mattermost/plt-2388
PLT-2388 Visiting invalid URLs and bad APIs causes redirect to error page
Этот коммит содержится в:
@@ -27,6 +27,8 @@ func InitApi() {
|
|||||||
InitWebhook(r)
|
InitWebhook(r)
|
||||||
InitPreference(r)
|
InitPreference(r)
|
||||||
InitLicense(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()
|
utils.InitHTML()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -476,25 +476,23 @@ func IsPrivateIpAddress(ipAddress string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) {
|
func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) {
|
||||||
T, locale := utils.GetTranslationsAndLocale(w, r)
|
T, _ := 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, "/")
|
title := T("api.templates.error.title", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
|
||||||
if len(pathParts) > 1 {
|
message := err.Message
|
||||||
page.Props["SiteURL"] = GetProtocol(r) + "://" + r.Host + "/" + pathParts[1]
|
details := err.DetailedError
|
||||||
} else {
|
link := "/"
|
||||||
page.Props["SiteURL"] = GetProtocol(r) + "://" + r.Host
|
linkMessage := T("api.templates.error.link")
|
||||||
}
|
|
||||||
|
|
||||||
page.Props["Title"] = T("api.templates.error.title", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
|
http.Redirect(
|
||||||
page.Props["Link"] = T("api.templates.error.link")
|
w,
|
||||||
|
r,
|
||||||
w.WriteHeader(err.StatusCode)
|
"/error?title="+url.QueryEscape(title)+
|
||||||
if rErr := page.RenderToWriter(w); rErr != nil {
|
"&message="+url.QueryEscape(message)+
|
||||||
l4g.Error("Failed to create error page: " + rErr.Error() + ", Original error: " + err.Error())
|
"&details="+url.QueryEscape(details)+
|
||||||
}
|
"&link="+url.QueryEscape(link)+
|
||||||
|
"&linkmessage="+url.QueryEscape(linkMessage),
|
||||||
|
http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Handle404(w http.ResponseWriter, r *http.Request) {
|
func Handle404(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
24
i18n/en.json
24
i18n/en.json
@@ -3734,5 +3734,29 @@
|
|||||||
{
|
{
|
||||||
"id": "web.watcher_fail.error",
|
"id": "web.watcher_fail.error",
|
||||||
"translation": "Failed to add directory to watcher %v"
|
"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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
{{define "error"}}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
{{template "head" . }}
|
|
||||||
<body class="sticky error">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="error__container">
|
|
||||||
<div class="error__icon">
|
|
||||||
<i class="fa fa-exclamation-triangle"/>
|
|
||||||
</div>
|
|
||||||
<h2>{{.Props.Title}}</h2>
|
|
||||||
<p>{{ .Props.Message }}</p>
|
|
||||||
<a href="{{.Props.SiteURL}}">{{.Props.Link}}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
<script>
|
|
||||||
var details = {{ .Props.Details }};
|
|
||||||
if (details.length > 0) {
|
|
||||||
console.log("error details: " + details);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</html>
|
|
||||||
{{end}}
|
|
||||||
58
webapp/components/error_page.jsx
Обычный файл
58
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 (
|
||||||
|
<div className='container-fluid'>
|
||||||
|
<div className='error__container'>
|
||||||
|
<div className='error__icon'>
|
||||||
|
<i className='fa fa-exclamation-triangle'/>
|
||||||
|
</div>
|
||||||
|
<h2>{title}</h2>
|
||||||
|
<p>{message}</p>
|
||||||
|
<Link to={link}>{linkMessage}</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorPage.defaultProps = {
|
||||||
|
};
|
||||||
|
ErrorPage.propTypes = {
|
||||||
|
location: React.PropTypes.object
|
||||||
|
};
|
||||||
@@ -10,7 +10,7 @@ import 'sass/styles.scss';
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
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 Root from 'components/root.jsx';
|
||||||
import LoggedIn from 'components/logged_in.jsx';
|
import LoggedIn from 'components/logged_in.jsx';
|
||||||
import NotLoggedIn from 'components/not_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 SignupTeam from 'components/signup_team.jsx';
|
||||||
import * as Client from 'utils/client.jsx';
|
import * as Client from 'utils/client.jsx';
|
||||||
import * as Websockets from 'action_creators/websocket_actions.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 * as GlobalActions from 'action_creators/global_actions.jsx';
|
||||||
import SignupTeamConfirm from 'components/signup_team_confirm.jsx';
|
import SignupTeamConfirm from 'components/signup_team_confirm.jsx';
|
||||||
import SignupUserComplete from 'components/signup_user_complete.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 AddIntegration from 'components/backstage/add_integration.jsx';
|
||||||
import AddIncomingWebhook from 'components/backstage/add_incoming_webhook.jsx';
|
import AddIncomingWebhook from 'components/backstage/add_incoming_webhook.jsx';
|
||||||
import AddOutgoingWebhook from 'components/backstage/add_outgoing_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 SignupTeamComplete from 'components/signup_team_complete/components/signup_team_complete.jsx';
|
||||||
import WelcomePage from 'components/signup_team_complete/components/team_signup_welcome_page.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';
|
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 is for anything that needs to be done for ALL react components.
|
||||||
// This runs before we start to render anything.
|
// This runs before we start to render anything.
|
||||||
function preRenderSetup(callwhendone) {
|
function preRenderSetup(callwhendone) {
|
||||||
@@ -218,6 +227,10 @@ function renderRootComponent() {
|
|||||||
component={Root}
|
component={Root}
|
||||||
onEnter={onRootEnter}
|
onEnter={onRootEnter}
|
||||||
>
|
>
|
||||||
|
<Route
|
||||||
|
path='error'
|
||||||
|
component={ErrorPage}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
component={LoggedIn}
|
component={LoggedIn}
|
||||||
onEnter={preLoggedIn}
|
onEnter={preLoggedIn}
|
||||||
@@ -285,6 +298,11 @@ function renderRootComponent() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
|
<Redirect
|
||||||
|
from='*'
|
||||||
|
to='/error'
|
||||||
|
query={notFoundParams}
|
||||||
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route
|
<Route
|
||||||
path='admin_console'
|
path='admin_console'
|
||||||
@@ -380,6 +398,11 @@ function renderRootComponent() {
|
|||||||
component={LDAPToEmail}
|
component={LDAPToEmail}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
|
<Redirect
|
||||||
|
from='*'
|
||||||
|
to='/error'
|
||||||
|
query={notFoundParams}
|
||||||
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user