From 7424f27cf30065a1308aa3ba4d75ea82c0af4af9 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 10 May 2024 20:07:01 +0800 Subject: [PATCH] Check if reverse proxy is correctly configured (#30890) Follow #27011 Follow #30885 --------- Co-authored-by: silverwind Co-authored-by: Giteabot --- options/locale/locale_en-US.ini | 1 + routers/web/admin/admin.go | 12 ++++++++++ routers/web/admin/admin_test.go | 24 ++++++++++++++++++++ routers/web/web.go | 1 + services/context/base.go | 3 ++- services/contexttest/context_tests.go | 2 +- templates/admin/self_check.tmpl | 28 ++++++++++++----------- web_src/js/bootstrap.js | 6 ++--- web_src/js/features/admin/selfcheck.js | 31 ++++++++++++++++++++++++++ web_src/js/features/common-global.js | 2 +- web_src/js/index.js | 2 ++ 11 files changed, 93 insertions(+), 19 deletions(-) create mode 100644 web_src/js/features/admin/selfcheck.js diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index eef4f5696a..6a08041a7c 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3320,6 +3320,7 @@ self_check.database_collation_case_insensitive = Database is using a collation % self_check.database_inconsistent_collation_columns = Database is using collation %s, but these columns are using mismatched collations. It might cause some unexpected problems. self_check.database_fix_mysql = For MySQL/MariaDB users, you could use the "gitea doctor convert" command to fix the collation problems, or you could also fix the problem by "ALTER ... COLLATE ..." SQLs manually. self_check.database_fix_mssql = For MSSQL users, you could only fix the problem by "ALTER ... COLLATE ..." SQLs manually at the moment. +self_check.location_origin_mismatch = Current URL (%[1]s) doesn't match the URL seen by Gitea (%[2]s). If you are using a reverse proxy, please make sure the "Host" and "X-Forwarded-Proto" headers are set correctly. [action] create_repo = created repository %s diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go index 3dd3c9670f..dee1650b5a 100644 --- a/routers/web/admin/admin.go +++ b/routers/web/admin/admin.go @@ -9,12 +9,14 @@ import ( "net/http" "runtime" "sort" + "strings" "time" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -223,6 +225,16 @@ func SelfCheck(ctx *context.Context) { ctx.HTML(http.StatusOK, tplSelfCheck) } +func SelfCheckPost(ctx *context.Context) { + var problems []string + frontendAppURL := ctx.FormString("location_origin") + setting.AppSubURL + "/" + ctxAppURL := httplib.GuessCurrentAppURL(ctx) + if !strings.HasPrefix(ctxAppURL, frontendAppURL) { + problems = append(problems, ctx.Locale.TrString("admin.self_check.location_origin_mismatch", frontendAppURL, ctxAppURL)) + } + ctx.JSON(http.StatusOK, map[string]any{"problems": problems}) +} + func CronTasks(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.monitor.cron") ctx.Data["PageIsAdminMonitorCron"] = true diff --git a/routers/web/admin/admin_test.go b/routers/web/admin/admin_test.go index 2b65ab3ea3..782126adf5 100644 --- a/routers/web/admin/admin_test.go +++ b/routers/web/admin/admin_test.go @@ -4,8 +4,14 @@ package admin import ( + "net/http" "testing" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/services/contexttest" + "github.com/stretchr/testify/assert" ) @@ -66,3 +72,21 @@ func TestShadowPassword(t *testing.T) { assert.EqualValues(t, k.Result, shadowPassword(k.Provider, k.CfgItem)) } } + +func TestSelfCheckPost(t *testing.T) { + defer test.MockVariableValue(&setting.AppURL, "http://config/sub/")() + defer test.MockVariableValue(&setting.AppSubURL, "/sub")() + + ctx, resp := contexttest.MockContext(t, "GET http://host/sub/admin/self_check?location_origin=http://frontend") + SelfCheckPost(ctx) + assert.EqualValues(t, http.StatusOK, resp.Code) + + data := struct { + Problems []string `json:"problems"` + }{} + err := json.Unmarshal(resp.Body.Bytes(), &data) + assert.NoError(t, err) + assert.Equal(t, []string{ + ctx.Locale.TrString("admin.self_check.location_origin_mismatch", "http://frontend/sub/", "http://host/sub/"), + }, data.Problems) +} diff --git a/routers/web/web.go b/routers/web/web.go index e1482c1e4a..f3b9969059 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -686,6 +686,7 @@ func registerRoutes(m *web.Route) { m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost) m.Get("/self_check", admin.SelfCheck) + m.Post("/self_check", admin.SelfCheckPost) m.Group("/config", func() { m.Get("", admin.Config) diff --git a/services/context/base.go b/services/context/base.go index 29e62ae389..23f0bcfc33 100644 --- a/services/context/base.go +++ b/services/context/base.go @@ -309,7 +309,8 @@ func NewBaseContext(resp http.ResponseWriter, req *http.Request) (b *Base, close Locale: middleware.Locale(resp, req), Data: middleware.GetContextData(req.Context()), } - b.AppendContextValue(translation.ContextKey, b.Locale) b.Req = b.Req.WithContext(b) + b.AppendContextValue(translation.ContextKey, b.Locale) + b.AppendContextValue(httplib.RequestContextKey, b.Req) return b, b.cleanUp } diff --git a/services/contexttest/context_tests.go b/services/contexttest/context_tests.go index 5624d24058..3c3fa76e3c 100644 --- a/services/contexttest/context_tests.go +++ b/services/contexttest/context_tests.go @@ -39,7 +39,7 @@ func mockRequest(t *testing.T, reqPath string) *http.Request { } requestURL, err := url.Parse(path) assert.NoError(t, err) - req := &http.Request{Method: method, URL: requestURL, Form: maps.Clone(requestURL.Query()), Header: http.Header{}} + req := &http.Request{Method: method, Host: requestURL.Host, URL: requestURL, Form: maps.Clone(requestURL.Query()), Header: http.Header{}} req = req.WithContext(middleware.WithContextData(req.Context())) return req } diff --git a/templates/admin/self_check.tmpl b/templates/admin/self_check.tmpl index a6c2ac1ac9..b249bf228e 100644 --- a/templates/admin/self_check.tmpl +++ b/templates/admin/self_check.tmpl @@ -1,4 +1,4 @@ -{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin config")}} +{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin")}}

@@ -6,7 +6,7 @@

{{if .StartupProblems}} -
+
{{ctx.Locale.Tr "admin.self_check.startup_warnings"}}
    {{range .StartupProblems}}
  • {{.}}
  • {{end}}
@@ -14,8 +14,10 @@
{{end}} +
+ {{if .DatabaseCheckHasProblems}} -
+
{{if .DatabaseType.IsMySQL}}
{{ctx.Locale.Tr "admin.self_check.database_fix_mysql"}}
{{else if .DatabaseType.IsMSSQL}} @@ -29,22 +31,22 @@ {{end}} {{if .DatabaseCheckInconsistentCollationColumns}}
- {{ctx.Locale.Tr "admin.self_check.database_inconsistent_collation_columns" .DatabaseCheckResult.DatabaseCollation}} -
    - {{range .DatabaseCheckInconsistentCollationColumns}} -
  • {{.}}
  • - {{end}} -
+
+ {{ctx.Locale.Tr "admin.self_check.database_inconsistent_collation_columns" .DatabaseCheckResult.DatabaseCollation}} +
    + {{range .DatabaseCheckInconsistentCollationColumns}} +
  • {{.}}
  • + {{end}} +
+
{{end}}
{{end}} - - {{if and (not .StartupProblems) (not .DatabaseCheckHasProblems)}} -
+ {{/* only shown when there is no visible "self-check-problem" */}} +
{{ctx.Locale.Tr "admin.self_check.no_problem_found"}}
- {{end}}
{{template "admin/layout_footer" .}} diff --git a/web_src/js/bootstrap.js b/web_src/js/bootstrap.js index 8339b4bd82..26627dfded 100644 --- a/web_src/js/bootstrap.js +++ b/web_src/js/bootstrap.js @@ -16,20 +16,20 @@ function shouldIgnoreError(err) { return false; } -export function showGlobalErrorMessage(msg) { +export function showGlobalErrorMessage(msg, msgType = 'error') { const msgContainer = document.querySelector('.page-content') ?? document.body; const msgCompact = msg.replace(/\W/g, '').trim(); // compact the message to a data attribute to avoid too many duplicated messages let msgDiv = msgContainer.querySelector(`.js-global-error[data-global-error-msg-compact="${msgCompact}"]`); if (!msgDiv) { const el = document.createElement('div'); - el.innerHTML = `
`; + el.innerHTML = `
`; msgDiv = el.childNodes[0]; } // merge duplicated messages into "the message (count)" format const msgCount = Number(msgDiv.getAttribute(`data-global-error-msg-count`)) + 1; msgDiv.setAttribute(`data-global-error-msg-compact`, msgCompact); msgDiv.setAttribute(`data-global-error-msg-count`, msgCount.toString()); - msgDiv.textContent = msg + (msgCount > 1 ? ` (${msgCount})` : ''); + msgDiv.querySelector('.ui.message').textContent = msg + (msgCount > 1 ? ` (${msgCount})` : ''); msgContainer.prepend(msgDiv); } diff --git a/web_src/js/features/admin/selfcheck.js b/web_src/js/features/admin/selfcheck.js new file mode 100644 index 0000000000..699395b363 --- /dev/null +++ b/web_src/js/features/admin/selfcheck.js @@ -0,0 +1,31 @@ +import {toggleElem} from '../../utils/dom.js'; +import {POST} from '../../modules/fetch.js'; + +const {appSubUrl} = window.config; + +export async function initAdminSelfCheck() { + const elCheckByFrontend = document.querySelector('#self-check-by-frontend'); + if (!elCheckByFrontend) return; + + const elContent = document.querySelector('.page-content.admin .admin-setting-content'); + + // send frontend self-check request + const resp = await POST(`${appSubUrl}/admin/self_check`, { + data: new URLSearchParams({ + location_origin: window.location.origin, + now: Date.now(), // TODO: check time difference between server and client + }), + }); + const json = await resp.json(); + toggleElem(elCheckByFrontend, Boolean(json.problems?.length)); + for (const problem of json.problems ?? []) { + const elProblem = document.createElement('div'); + elProblem.classList.add('ui', 'warning', 'message'); + elProblem.textContent = problem; + elCheckByFrontend.append(elProblem); + } + + // only show the "no problem" if there is no visible "self-check-problem" + const hasProblem = Boolean(elContent.querySelectorAll('.self-check-problem:not(.tw-hidden)').length); + toggleElem(elContent.querySelector('.self-check-no-problem'), !hasProblem); +} diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index 5b8673105d..3b021d4485 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -451,5 +451,5 @@ export function checkAppUrl() { return; } showGlobalErrorMessage(`Your ROOT_URL in app.ini is "${appUrl}", it's unlikely matching the site you are visiting. -Mismatched ROOT_URL config causes wrong URL links for web UI/mail content/webhook notification/OAuth2 sign-in.`); +Mismatched ROOT_URL config causes wrong URL links for web UI/mail content/webhook notification/OAuth2 sign-in.`, 'warning'); } diff --git a/web_src/js/index.js b/web_src/js/index.js index fc2f6b9b0b..1867556eee 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -87,6 +87,7 @@ import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js' import {initDirAuto} from './modules/dirauto.js'; import {initRepositorySearch} from './features/repo-search.js'; import {initColorPickers} from './features/colorpicker.js'; +import {initAdminSelfCheck} from './features/admin/selfcheck.js'; // Init Gitea's Fomantic settings initGiteaFomantic(); @@ -132,6 +133,7 @@ onDomReady(() => { initAdminEmails(); initAdminUserListSearchForm(); initAdminConfigs(); + initAdminSelfCheck(); initDashboardRepoList();