Skip to content

Add post-installation redirect based on admin account status #34493

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 14 additions & 0 deletions models/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,20 @@ type CountUserFilter struct {
IsActive optional.Option[bool]
}

// HasUsers checks whether there are any users in the database, or only one user exists.
func HasUsers(ctx context.Context) (ret struct {
HasAnyUser, HasOnlyOneUser bool
}, err error,
) {
res, err := db.GetEngine(ctx).Table(&User{}).Cols("id").Limit(2).Query()
if err != nil {
return ret, fmt.Errorf("error checking user existence: %w", err)
}
ret.HasAnyUser = len(res) != 0
ret.HasOnlyOneUser = len(res) == 1
return ret, nil
}

// CountUsers returns number of users.
func CountUsers(ctx context.Context, opts *CountUserFilter) int64 {
return countUsers(ctx, opts)
Expand Down
1 change: 1 addition & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ remember_me.compromised = The login token is not valid anymore which may indicat
forgot_password_title= Forgot Password
forgot_password = Forgot password?
need_account = Need an account?
sign_up_tip = You are registering the first account in the system, which has administrator privileges. Please carefully remember your username and password, as forgetting these credentials may require system reset and reinitialization.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as forgetting these credentials may require system reset and reinitialization.

That's not true, site admin can use gitea CLI to reset user password

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are registering the first account in the system. Please remember your username and password, as this account typically has administrative privileges. How about this? Is it okay?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo the wording is fine..? If the sysadmin and the first registered user are not the same person (e.g. as a SaaS platform without terminal access), the first registered user may not be able to access the gitea cli.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the first registered user may not be able to access the gitea cli.

  • Why they can't contact their site admin with CLI access?
  • If the "first" user doesn't have CLI access, then how could they do "system reset and reinitialization" as the message says if they forget password?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g. as a SaaS platform without terminal access

As a SaaS platform, isn't there a separate instance management console to help to reset the password? For example, gitea cloud?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a complete and feasible plan? For example: some users use the "install" page, while some others don't, they just prepare the app.ini manually and set INSTALL_LOCK=true?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. There are only two ways to install Gitea.
If we changed the rule that the first user is admin. Then for web installation, an admin account creation should be required. For command line installation, they have to create the first user from command line gitea admin instead of registering the first account from web UI.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does helmchart also work this way?

Copy link
Contributor

@ChristopherHX ChristopherHX Jun 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does helmchart also work this way?

You can make the helmchart skip admin user creation from CLI

Yes by default yes it follows the cli setup and admin creation from gitea admin, but it seems to be currently possible to change your values.yml to set the admin username/password to an empty string instead of using the preset admin username & password.

In that case you could indeed end up in a without admin user state in the helm chart and have to attach via kubectl to the gitea container for the admin account, but you can just update the admin password / create the admin later as well by redeploying/updating the helmchart.

Ref: https://gitea.com/gitea/helm-gitea/src/commit/0d532363ebef69e2baedbb8b9370519b373b5394/templates/gitea/init.yaml#L82, https://gitea.com/gitea/helm-gitea/src/commit/0d532363ebef69e2baedbb8b9370519b373b5394/values.yaml#L353

Copy link
Member

@BLumia BLumia Jun 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the "first" user doesn't have CLI access, then how could they do "system reset and reinitialization" as the message says if they forget password?

The first user who accessed the webpage to register account might not be the same person who does sysadmin. The person also might be able to reach out to a separated sysadmin person/team to do "system reset and reinitialization", by either via email or other method they have.

What I mean is since the copywriting is "may", so it's likely fine (or maybe we can also change it to "might"). We simply cannot assume the person who control site admin account is the same person who can access the server console to use gitea cli.

sign_up_now = Register now.
sign_up_successful = Account was successfully created. Welcome!
confirmation_mail_sent_prompt_ex = A new confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the registration process. If your registration email address is incorrect, you can sign in again and change it.
Expand Down
2 changes: 2 additions & 0 deletions routers/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,5 +607,7 @@ func SubmitInstall(ctx *context.Context) {
// InstallDone shows the "post-install" page, makes it easier to develop the page.
// The name is not called as "PostInstall" to avoid misinterpretation as a handler for "POST /install"
func InstallDone(ctx *context.Context) { //nolint
hasUsers, _ := user_model.HasUsers(ctx)
ctx.Data["IsAccountCreated"] = hasUsers.HasAnyUser
ctx.HTML(http.StatusOK, tplPostInstall)
}
12 changes: 10 additions & 2 deletions routers/web/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,9 +421,11 @@ func SignOut(ctx *context.Context) {
// SignUp render the register page
func SignUp(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("sign_up")

ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"

hasUsers, _ := user_model.HasUsers(ctx)
ctx.Data["IsFirstTimeRegistration"] = !hasUsers.HasAnyUser

oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
if err != nil {
ctx.ServerError("UserSignUp", err)
Expand Down Expand Up @@ -610,7 +612,13 @@ func createUserInContext(ctx *context.Context, tpl templates.TplName, form any,
// sends a confirmation email if required.
func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) {
// Auto-set admin for the only user.
if user_model.CountUsers(ctx, nil) == 1 {
hasUsers, err := user_model.HasUsers(ctx)
if err != nil {
ctx.ServerError("HasUsers", err)
return false
}
if hasUsers.HasOnlyOneUser {
// the only user is the one just created, will set it as admin
opts := &user_service.UpdateOptions{
IsActive: optional.Some(true),
IsAdmin: optional.Some(true),
Expand Down
2 changes: 1 addition & 1 deletion templates/post-install.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<!-- the "cup" has a handler, so move it a little leftward to make it visually in the center -->
<div class="tw-ml-[-30px]"><img width="160" src="{{AssetUrlPrefix}}/img/loading.png" alt aria-hidden="true"></div>
<div class="tw-my-[2em] tw-text-[18px]">
<a id="goto-user-login" href="{{AppSubUrl}}/user/login">{{ctx.Locale.Tr "install.installing_desc"}}</a>
<a id="goto-after-install" href="{{AppSubUrl}}{{Iif .IsAccountCreated "/user/login" "/user/sign_up"}}">{{ctx.Locale.Tr "install.installing_desc"}}</a>
</div>
</div>
</div>
Expand Down
3 changes: 3 additions & 0 deletions templates/user/auth/signup_inner.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
{{end}}
</h4>
<div class="ui attached segment">
{{if .IsFirstTimeRegistration}}
<p>{{ctx.Locale.Tr "auth.sign_up_tip"}}</p>
{{end}}
<form class="ui form" action="{{.SignUpLink}}" method="post">
{{.CsrfTokenHtml}}
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister)}}
Expand Down
2 changes: 1 addition & 1 deletion web_src/js/features/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function initPreInstall() {
}

function initPostInstall() {
const el = document.querySelector('#goto-user-login');
const el = document.querySelector('#goto-after-install');
if (!el) return;

const targetUrl = el.getAttribute('href');
Expand Down