]> source.dussan.org Git - gitea.git/commitdiff
Check if reverse proxy is correctly configured (#30890) (#30935)
authorGiteabot <teabot@gitea.io>
Fri, 10 May 2024 12:34:04 +0000 (20:34 +0800)
committerGitHub <noreply@github.com>
Fri, 10 May 2024 12:34:04 +0000 (12:34 +0000)
Backport #30890 by wxiaoguang

Follow #27011
Follow #30885

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
options/locale/locale_en-US.ini
routers/web/admin/admin.go
routers/web/admin/admin_test.go
routers/web/web.go
services/context/base.go
services/contexttest/context_tests.go
templates/admin/self_check.tmpl
web_src/js/bootstrap.js
web_src/js/features/admin/selfcheck.js [new file with mode: 0644]
web_src/js/features/common-global.js
web_src/js/index.js

index fb591be393159968a001b05270cbcdbadaa86e9e..f4a675696458a2a38bbf3b4a1562ec46435ad3e8 100644 (file)
@@ -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 <a href="%s">%s</a>
index e6585d8833bc3d1c665d46eb9f2100b752a53501..5edf58377fff478a0654243dc5748c80346b4554 100644 (file)
@@ -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
index 2b65ab3ea37ec26ad0859f22c43377a6cb2f419b..782126adf5c38ce6c20082ac6f4e539697f692a0 100644 (file)
@@ -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)
+}
index e1482c1e4ada5287f9770526c4bd72ce2f708edc..f3b9969059bdaf6afc9ed52833616b58edd2031e 100644 (file)
@@ -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)
index 29e62ae38924aeaab0296fe5f65dd42966323c67..23f0bcfc33ba6e938ba3a75621dabde02f3790a9 100644 (file)
@@ -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
 }
index 5624d24058727891c601ce5256a3337a0ae97f54..3c3fa76e3c49f15f057b9c82bd938328033f9efc 100644 (file)
@@ -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
 }
index a6c2ac1ac9de4a127514271646d034c5b8f37ec9..b249bf228ec1462cdb9395e693b6401695a3e7ff 100644 (file)
@@ -1,4 +1,4 @@
-{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin config")}}
+{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin")}}
 
 <div class="admin-setting-content">
        <h4 class="ui top attached header">
@@ -6,7 +6,7 @@
        </h4>
 
        {{if .StartupProblems}}
-       <div class="ui attached segment">
+       <div class="ui attached segment self-check-problem">
                <div class="ui warning message">
                        <div>{{ctx.Locale.Tr "admin.self_check.startup_warnings"}}</div>
                        <ul class="tw-w-full">{{range .StartupProblems}}<li>{{.}}</li>{{end}}</ul>
        </div>
        {{end}}
 
+       <div class="ui attached segment tw-hidden self-check-problem" id="self-check-by-frontend"></div>
+
        {{if .DatabaseCheckHasProblems}}
-       <div class="ui attached segment">
+       <div class="ui attached segment self-check-problem">
                {{if .DatabaseType.IsMySQL}}
                        <div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.database_fix_mysql"}}</div>
                {{else if .DatabaseType.IsMSSQL}}
                {{end}}
                {{if .DatabaseCheckInconsistentCollationColumns}}
                        <div class="ui red message">
-                               {{ctx.Locale.Tr "admin.self_check.database_inconsistent_collation_columns" .DatabaseCheckResult.DatabaseCollation}}
-                               <ul class="tw-w-full">
-                               {{range .DatabaseCheckInconsistentCollationColumns}}
-                                       <li>{{.}}</li>
-                               {{end}}
-                               </ul>
+                               <details>
+                                       <summary>{{ctx.Locale.Tr "admin.self_check.database_inconsistent_collation_columns" .DatabaseCheckResult.DatabaseCollation}}</summary>
+                                       <ul class="tw-w-full">
+                                       {{range .DatabaseCheckInconsistentCollationColumns}}
+                                               <li>{{.}}</li>
+                                       {{end}}
+                                       </ul>
+                               </details>
                        </div>
                {{end}}
        </div>
        {{end}}
-
-       {{if and (not .StartupProblems) (not .DatabaseCheckHasProblems)}}
-       <div class="ui attached segment">
+       {{/* only shown when there is no visible "self-check-problem" */}}
+       <div class="ui attached segment tw-hidden self-check-no-problem">
                {{ctx.Locale.Tr "admin.self_check.no_problem_found"}}
        </div>
-       {{end}}
 </div>
 
 {{template "admin/layout_footer" .}}
index 8339b4bd825c2698a2bd3beed1ab10749aefde9d..26627dfdedbb013a76ac59c7142cc74e77d6b502 100644 (file)
@@ -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 = `<div class="ui container negative message center aligned js-global-error tw-mt-[15px] tw-whitespace-pre-line"></div>`;
+    el.innerHTML = `<div class="ui container js-global-error tw-my-[--page-spacing]"><div class="ui ${msgType} message tw-text-center tw-whitespace-pre-line"></div></div>`;
     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 (file)
index 0000000..699395b
--- /dev/null
@@ -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);
+}
index 5b8673105d6b2eb89498737eba29981b1fecc546..3b021d44851a2b6628d2c1c5ccaf363dc34b13b0 100644 (file)
@@ -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');
 }
index fc2f6b9b0b91959bb227ba418dae042b4e7eabb1..1867556eeed21ef95dcacccd9cce2fba33e9f8ed 100644 (file)
@@ -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();