Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check if reverse proxy is correctly configured #30890

Merged
merged 4 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
12 changes: 12 additions & 0 deletions routers/web/admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions routers/web/admin/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)
}
1 change: 1 addition & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,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)
Expand Down
3 changes: 2 additions & 1 deletion services/context/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion services/contexttest/context_tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
28 changes: 15 additions & 13 deletions templates/admin/self_check.tmpl
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
{{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">
{{ctx.Locale.Tr "admin.self_check"}}
</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>
</div>
{{end}}

<div class="ui attached segment tw-hidden self-check-problem 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}}
Expand All @@ -29,22 +31,22 @@
{{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" .}}
6 changes: 3 additions & 3 deletions web_src/js/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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-4"><div class="ui ${msgType} message tw-text-center tw-whitespace-pre-line"></div></div>`;
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
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);
}

Expand Down
31 changes: 31 additions & 0 deletions web_src/js/features/admin/selfcheck.js
Original file line number Diff line number Diff line change
@@ -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('.page-content.admin .self-check-by-frontend');
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
if (!elCheckByFrontend) return;

const elContent = document.querySelector('.page-content.admin .admin-setting-content');
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved

// 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
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
}),
});
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);
}
2 changes: 1 addition & 1 deletion web_src/js/features/common-global.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
2 changes: 2 additions & 0 deletions web_src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -132,6 +133,7 @@ onDomReady(() => {
initAdminEmails();
initAdminUserListSearchForm();
initAdminConfigs();
initAdminSelfCheck();

initDashboardRepoList();

Expand Down