+1
-1
cmd/admin_auth_ldap.go
+1
-1
cmd/admin_auth_ldap.go
···
386
386
return a.createAuthSource(ctx, authSource)
387
387
}
388
388
389
-
// updateLdapBindDn updates a new LDAP (simple auth) authentication source.
389
+
// updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source.
390
390
func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
391
391
ctx, cancel := installSignals()
392
392
defer cancel()
+18
custom/conf/app.example.ini
+18
custom/conf/app.example.ini
···
925
925
;; Valid site url schemes for user profiles
926
926
;VALID_SITE_URL_SCHEMES=http,https
927
927
928
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
929
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
930
+
;[service.explore]
931
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
932
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
933
+
;;
934
+
;; Only allow signed in users to view the explore pages.
935
+
;REQUIRE_SIGNIN_VIEW = false
936
+
;;
937
+
;; Disable the users explore page.
938
+
;DISABLE_USERS_PAGE = false
939
+
;;
940
+
;; Disable the organizations explore page.
941
+
;DISABLE_ORGANIZATIONS_PAGE = false
942
+
;;
943
+
;; Disable the code explore page.
944
+
;DISABLE_CODE_PAGE = false
945
+
;;
928
946
929
947
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
930
948
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+1
-1
models/issues/issue.go
+1
-1
models/issues/issue.go
···
875
875
return issues, nil
876
876
}
877
877
878
-
// IsNewPinnedAllowed returns if a new Issue or Pull request can be pinned
878
+
// IsNewPinAllowed returns if a new Issue or Pull request can be pinned
879
879
func IsNewPinAllowed(ctx context.Context, repoID int64, isPull bool) (bool, error) {
880
880
var maxPin int
881
881
_, err := db.GetEngine(ctx).SQL("SELECT COUNT(pin_order) FROM issue WHERE repo_id = ? AND is_pull = ? AND pin_order > 0", repoID, isPull).Get(&maxPin)
+1
-1
models/issues/pull.go
+1
-1
models/issues/pull.go
···
689
689
return pr, pr.LoadAttributes(ctx)
690
690
}
691
691
692
-
// GetPullRequestsByBaseHeadInfo returns the pull request by given base and head
692
+
// GetPullRequestByBaseHeadInfo returns the pull request by given base and head
693
693
func GetPullRequestByBaseHeadInfo(ctx context.Context, baseID, headID int64, base, head string) (*PullRequest, error) {
694
694
pr := &PullRequest{}
695
695
sess := db.GetEngine(ctx).
+4
-2
modules/setting/service.go
+4
-2
modules/setting/service.go
···
91
91
92
92
// Explore page settings
93
93
Explore struct {
94
-
RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"`
95
-
DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"`
94
+
RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"`
95
+
DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"`
96
+
DisableOrganizationsPage bool `ini:"DISABLE_ORGANIZATIONS_PAGE"`
97
+
DisableCodePage bool `ini:"DISABLE_CODE_PAGE"`
96
98
} `ini:"service.explore"`
97
99
}{
98
100
AllowedUserVisibilityModesSlice: []bool{true, true, true},
+3
release-notes/5714.md
+3
release-notes/5714.md
···
1
+
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/d13a4ab5632d6a9697bd0907f9c69ed57d949340) Fixed a bug related to disabling two-factor authentication
2
+
chore: [commit](https://codeberg.org/forgejo/forgejo/commit/ab26d880932dbc116c43ea277029984c7a6d4e94) Emit a log message when failing to delete an inactive user
3
+
feat: [commit](https://codeberg.org/forgejo/forgejo/commit/ab660c5944d59cdb4ecc071401445ac9f53cee45) Add `DISABLE_ORGANIZATIONS_PAGE` and `DISABLE_CODE_PAGE` settings for explore pages
+10
-2
routers/api/v1/api.go
+10
-2
routers/api/v1/api.go
···
395
395
396
396
func reqExploreSignIn() func(ctx *context.APIContext) {
397
397
return func(ctx *context.APIContext) {
398
-
if setting.Service.Explore.RequireSigninView && !ctx.IsSigned {
398
+
if (setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned {
399
399
ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users")
400
+
}
401
+
}
402
+
}
403
+
404
+
func reqUsersExploreEnabled() func(ctx *context.APIContext) {
405
+
return func(ctx *context.APIContext) {
406
+
if setting.Service.Explore.DisableUsersPage {
407
+
ctx.NotFound()
400
408
}
401
409
}
402
410
}
···
887
895
888
896
// Users (requires user scope)
889
897
m.Group("/users", func() {
890
-
m.Get("/search", reqExploreSignIn(), user.Search)
898
+
m.Get("/search", reqExploreSignIn(), reqUsersExploreEnabled(), user.Search)
891
899
892
900
m.Group("/{username}", func() {
893
901
m.Get("", reqExploreSignIn(), user.GetInfo)
+3
-2
routers/web/explore/code.go
+3
-2
routers/web/explore/code.go
···
21
21
22
22
// Code render explore code page
23
23
func Code(ctx *context.Context) {
24
-
if !setting.Indexer.RepoIndexerEnabled {
24
+
if !setting.Indexer.RepoIndexerEnabled || setting.Service.Explore.DisableCodePage {
25
25
ctx.Redirect(setting.AppSubURL + "/explore")
26
26
return
27
27
}
28
28
29
-
ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage
29
+
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
30
+
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
30
31
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
31
32
ctx.Data["Title"] = ctx.Tr("explore")
32
33
ctx.Data["PageIsExplore"] = true
+7
-1
routers/web/explore/org.go
+7
-1
routers/web/explore/org.go
···
14
14
15
15
// Organizations render explore organizations page
16
16
func Organizations(ctx *context.Context) {
17
-
ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage
17
+
if setting.Service.Explore.DisableOrganizationsPage {
18
+
ctx.Redirect(setting.AppSubURL + "/explore")
19
+
return
20
+
}
21
+
22
+
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
23
+
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
18
24
ctx.Data["Title"] = ctx.Tr("explore")
19
25
ctx.Data["PageIsExplore"] = true
20
26
ctx.Data["PageIsExploreOrganizations"] = true
+3
-1
routers/web/explore/repo.go
+3
-1
routers/web/explore/repo.go
···
165
165
166
166
// Repos render explore repositories page
167
167
func Repos(ctx *context.Context) {
168
-
ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage
168
+
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
169
+
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
170
+
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
169
171
ctx.Data["Title"] = ctx.Tr("explore")
170
172
ctx.Data["PageIsExplore"] = true
171
173
ctx.Data["PageIsExploreRepositories"] = true
+3
-1
routers/web/explore/user.go
+3
-1
routers/web/explore/user.go
···
131
131
// Users render explore users page
132
132
func Users(ctx *context.Context) {
133
133
if setting.Service.Explore.DisableUsersPage {
134
-
ctx.Redirect(setting.AppSubURL + "/explore/repos")
134
+
ctx.Redirect(setting.AppSubURL + "/explore")
135
135
return
136
136
}
137
+
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
138
+
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
137
139
ctx.Data["Title"] = ctx.Tr("explore")
138
140
ctx.Data["PageIsExplore"] = true
139
141
ctx.Data["PageIsExploreUsers"] = true
+9
-22
routers/web/user/search.go
+9
-22
routers/web/user/search.go
···
8
8
9
9
"code.gitea.io/gitea/models/db"
10
10
user_model "code.gitea.io/gitea/models/user"
11
+
"code.gitea.io/gitea/modules/optional"
12
+
"code.gitea.io/gitea/modules/setting"
11
13
"code.gitea.io/gitea/services/context"
12
14
"code.gitea.io/gitea/services/convert"
13
15
)
14
16
15
-
// Search search users
16
-
func Search(ctx *context.Context) {
17
-
listOptions := db.ListOptions{
18
-
Page: ctx.FormInt("page"),
19
-
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
20
-
}
21
-
22
-
users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
17
+
// SearchCandidates searches candidate users for dropdown list
18
+
func SearchCandidates(ctx *context.Context) {
19
+
users, _, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
23
20
Actor: ctx.Doer,
24
21
Keyword: ctx.FormTrim("q"),
25
-
UID: ctx.FormInt64("uid"),
26
22
Type: user_model.UserTypeIndividual,
27
-
IsActive: ctx.FormOptionalBool("active"),
28
-
ListOptions: listOptions,
23
+
IsActive: optional.Some(true),
24
+
ListOptions: db.ListOptions{PageSize: setting.UI.MembersPagingNum},
29
25
})
30
26
if err != nil {
31
-
ctx.JSON(http.StatusInternalServerError, map[string]any{
32
-
"ok": false,
33
-
"error": err.Error(),
34
-
})
27
+
ctx.ServerError("Unable to search users", err)
35
28
return
36
29
}
37
-
38
-
ctx.SetTotalCountHeader(maxResults)
39
-
40
-
ctx.JSON(http.StatusOK, map[string]any{
41
-
"ok": true,
42
-
"data": convert.ToUsers(ctx, ctx.Doer, users),
43
-
})
30
+
ctx.JSON(http.StatusOK, map[string]any{"data": convert.ToUsers(ctx, ctx.Doer, users)})
44
31
}
+6
-3
routers/web/user/setting/security/2fa.go
+6
-3
routers/web/user/setting/security/2fa.go
···
34
34
if auth.IsErrTwoFactorNotEnrolled(err) {
35
35
ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled"))
36
36
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
37
+
} else {
38
+
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
37
39
}
38
-
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
39
40
return
40
41
}
41
42
···
64
65
if auth.IsErrTwoFactorNotEnrolled(err) {
65
66
ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled"))
66
67
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
68
+
} else {
69
+
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
67
70
}
68
-
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
69
71
return
70
72
}
71
73
···
74
76
// There is a potential DB race here - we must have been disabled by another request in the intervening period
75
77
ctx.Flash.Success(ctx.Tr("settings.twofa_disabled"))
76
78
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
79
+
} else {
80
+
ctx.ServerError("SettingsTwoFactor: Failed to DeleteTwoFactorByID", err)
77
81
}
78
-
ctx.ServerError("SettingsTwoFactor: Failed to DeleteTwoFactorByID", err)
79
82
return
80
83
}
81
84
+1
-1
routers/web/web.go
+1
-1
routers/web/web.go
···
642
642
m.Post("/logout", auth.SignOut)
643
643
m.Get("/task/{task}", reqSignIn, user.TaskStatus)
644
644
m.Get("/stopwatches", reqSignIn, user.GetStopwatches)
645
-
m.Get("/search", ignExploreSignIn, user.Search)
645
+
m.Get("/search_candidates", ignExploreSignIn, user.SearchCandidates)
646
646
m.Group("/oauth2", func() {
647
647
m.Get("/{provider}", auth.SignInOAuth)
648
648
m.Get("/{provider}/callback", auth.SignInOAuthCallback)
+1
services/user/user.go
+1
services/user/user.go
···
306
306
// Ignore users that were set inactive by admin.
307
307
if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) ||
308
308
models.IsErrUserOwnPackages(err) || models.IsErrDeleteLastAdminUser(err) {
309
+
log.Warn("Inactive user %q has repositories, organizations or packages, skipping deletion: %v", u.Name, err)
309
310
continue
310
311
}
311
312
return err
+12
-15
web_src/js/features/comp/SearchUserBox.js
+12
-15
web_src/js/features/comp/SearchUserBox.js
···
8
8
const searchUserBox = document.getElementById('search-user-box');
9
9
if (!searchUserBox) return;
10
10
11
-
const $searchUserBox = $(searchUserBox);
12
11
const allowEmailInput = searchUserBox.getAttribute('data-allow-email') === 'true';
13
12
const allowEmailDescription = searchUserBox.getAttribute('data-allow-email-description') ?? undefined;
14
-
$searchUserBox.search({
13
+
$(searchUserBox).search({
15
14
minCharacters: 2,
16
15
apiSettings: {
17
-
url: `${appSubUrl}/user/search?active=1&q={query}`,
16
+
url: `${appSubUrl}/user/search_candidates?q={query}`,
18
17
onResponse(response) {
19
-
const items = [];
20
-
const searchQuery = $searchUserBox.find('input').val();
18
+
const resultItems = [];
19
+
const searchQuery = searchUserBox.querySelector('input').value;
21
20
const searchQueryUppercase = searchQuery.toUpperCase();
22
-
$.each(response.data, (_i, item) => {
21
+
for (const item of response.data) {
23
22
const resultItem = {
24
23
title: item.login,
25
24
image: item.avatar_url,
25
+
description: htmlEscape(item.full_name),
26
26
};
27
-
if (item.full_name) {
28
-
resultItem.description = htmlEscape(item.full_name);
29
-
}
30
27
if (searchQueryUppercase === item.login.toUpperCase()) {
31
-
items.unshift(resultItem);
28
+
resultItems.unshift(resultItem); // add the exact match to the top
32
29
} else {
33
-
items.push(resultItem);
30
+
resultItems.push(resultItem);
34
31
}
35
-
});
32
+
}
36
33
37
-
if (allowEmailInput && !items.length && looksLikeEmailAddressCheck.test(searchQuery)) {
34
+
if (allowEmailInput && !resultItems.length && looksLikeEmailAddressCheck.test(searchQuery)) {
38
35
const resultItem = {
39
36
title: searchQuery,
40
37
description: allowEmailDescription,
41
38
};
42
-
items.push(resultItem);
39
+
resultItems.push(resultItem);
43
40
}
44
41
45
-
return {results: items};
42
+
return {results: resultItems};
46
43
},
47
44
},
48
45
searchFields: ['login', 'full_name'],