code.gitea.io/gitea@v1.22.3/routers/web/auth/auth.go (about) 1 // Copyright 2014 The Gogs Authors. All rights reserved. 2 // Copyright 2018 The Gitea Authors. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package auth 6 7 import ( 8 "errors" 9 "fmt" 10 "html/template" 11 "net/http" 12 "strings" 13 14 "code.gitea.io/gitea/models/auth" 15 "code.gitea.io/gitea/models/db" 16 user_model "code.gitea.io/gitea/models/user" 17 "code.gitea.io/gitea/modules/auth/password" 18 "code.gitea.io/gitea/modules/base" 19 "code.gitea.io/gitea/modules/eventsource" 20 "code.gitea.io/gitea/modules/httplib" 21 "code.gitea.io/gitea/modules/log" 22 "code.gitea.io/gitea/modules/optional" 23 "code.gitea.io/gitea/modules/session" 24 "code.gitea.io/gitea/modules/setting" 25 "code.gitea.io/gitea/modules/timeutil" 26 "code.gitea.io/gitea/modules/util" 27 "code.gitea.io/gitea/modules/web" 28 "code.gitea.io/gitea/modules/web/middleware" 29 auth_service "code.gitea.io/gitea/services/auth" 30 "code.gitea.io/gitea/services/auth/source/oauth2" 31 "code.gitea.io/gitea/services/context" 32 "code.gitea.io/gitea/services/externalaccount" 33 "code.gitea.io/gitea/services/forms" 34 "code.gitea.io/gitea/services/mailer" 35 user_service "code.gitea.io/gitea/services/user" 36 37 "github.com/markbates/goth" 38 ) 39 40 const ( 41 tplSignIn base.TplName = "user/auth/signin" // for sign in page 42 tplSignUp base.TplName = "user/auth/signup" // for sign up page 43 TplActivate base.TplName = "user/auth/activate" // for activate user 44 TplActivatePrompt base.TplName = "user/auth/activate_prompt" // for showing a message for user activation 45 ) 46 47 // autoSignIn reads cookie and try to auto-login. 48 func autoSignIn(ctx *context.Context) (bool, error) { 49 isSucceed := false 50 defer func() { 51 if !isSucceed { 52 ctx.DeleteSiteCookie(setting.CookieRememberName) 53 } 54 }() 55 56 if err := auth.DeleteExpiredAuthTokens(ctx); err != nil { 57 log.Error("Failed to delete expired auth tokens: %v", err) 58 } 59 60 t, err := auth_service.CheckAuthToken(ctx, ctx.GetSiteCookie(setting.CookieRememberName)) 61 if err != nil { 62 switch err { 63 case auth_service.ErrAuthTokenInvalidFormat, auth_service.ErrAuthTokenExpired: 64 return false, nil 65 } 66 return false, err 67 } 68 if t == nil { 69 return false, nil 70 } 71 72 u, err := user_model.GetUserByID(ctx, t.UserID) 73 if err != nil { 74 if !user_model.IsErrUserNotExist(err) { 75 return false, fmt.Errorf("GetUserByID: %w", err) 76 } 77 return false, nil 78 } 79 80 isSucceed = true 81 82 nt, token, err := auth_service.RegenerateAuthToken(ctx, t) 83 if err != nil { 84 return false, err 85 } 86 87 ctx.SetSiteCookie(setting.CookieRememberName, nt.ID+":"+token, setting.LogInRememberDays*timeutil.Day) 88 89 if err := updateSession(ctx, nil, map[string]any{ 90 // Set session IDs 91 "uid": u.ID, 92 "uname": u.Name, 93 }); err != nil { 94 return false, fmt.Errorf("unable to updateSession: %w", err) 95 } 96 97 if err := resetLocale(ctx, u); err != nil { 98 return false, err 99 } 100 101 ctx.Csrf.DeleteCookie(ctx) 102 return true, nil 103 } 104 105 func resetLocale(ctx *context.Context, u *user_model.User) error { 106 // Language setting of the user overwrites the one previously set 107 // If the user does not have a locale set, we save the current one. 108 if u.Language == "" { 109 opts := &user_service.UpdateOptions{ 110 Language: optional.Some(ctx.Locale.Language()), 111 } 112 if err := user_service.UpdateUser(ctx, u, opts); err != nil { 113 return err 114 } 115 } 116 117 middleware.SetLocaleCookie(ctx.Resp, u.Language, 0) 118 119 if ctx.Locale.Language() != u.Language { 120 ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req) 121 } 122 123 return nil 124 } 125 126 func RedirectAfterLogin(ctx *context.Context) { 127 redirectTo := ctx.FormString("redirect_to") 128 if redirectTo == "" { 129 redirectTo = ctx.GetSiteCookie("redirect_to") 130 } 131 middleware.DeleteRedirectToCookie(ctx.Resp) 132 nextRedirectTo := setting.AppSubURL + string(setting.LandingPageURL) 133 if setting.LandingPageURL == setting.LandingPageLogin { 134 nextRedirectTo = setting.AppSubURL + "/" // do not cycle-redirect to the login page 135 } 136 ctx.RedirectToCurrentSite(redirectTo, nextRedirectTo) 137 } 138 139 func CheckAutoLogin(ctx *context.Context) bool { 140 isSucceed, err := autoSignIn(ctx) // try to auto-login 141 if err != nil { 142 if errors.Is(err, auth_service.ErrAuthTokenInvalidHash) { 143 ctx.Flash.Error(ctx.Tr("auth.remember_me.compromised"), true) 144 return false 145 } 146 ctx.ServerError("autoSignIn", err) 147 return true 148 } 149 150 redirectTo := ctx.FormString("redirect_to") 151 if len(redirectTo) > 0 { 152 middleware.SetRedirectToCookie(ctx.Resp, redirectTo) 153 } 154 155 if isSucceed { 156 RedirectAfterLogin(ctx) 157 return true 158 } 159 160 return false 161 } 162 163 // SignIn render sign in page 164 func SignIn(ctx *context.Context) { 165 ctx.Data["Title"] = ctx.Tr("sign_in") 166 167 if CheckAutoLogin(ctx) { 168 return 169 } 170 171 if ctx.IsSigned { 172 RedirectAfterLogin(ctx) 173 return 174 } 175 176 oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true)) 177 if err != nil { 178 ctx.ServerError("UserSignIn", err) 179 return 180 } 181 ctx.Data["OAuth2Providers"] = oauth2Providers 182 ctx.Data["Title"] = ctx.Tr("sign_in") 183 ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" 184 ctx.Data["PageIsSignIn"] = true 185 ctx.Data["PageIsLogin"] = true 186 ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx) 187 188 if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin { 189 context.SetCaptchaData(ctx) 190 } 191 192 ctx.HTML(http.StatusOK, tplSignIn) 193 } 194 195 // SignInPost response for sign in request 196 func SignInPost(ctx *context.Context) { 197 ctx.Data["Title"] = ctx.Tr("sign_in") 198 199 oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true)) 200 if err != nil { 201 ctx.ServerError("UserSignIn", err) 202 return 203 } 204 ctx.Data["OAuth2Providers"] = oauth2Providers 205 ctx.Data["Title"] = ctx.Tr("sign_in") 206 ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" 207 ctx.Data["PageIsSignIn"] = true 208 ctx.Data["PageIsLogin"] = true 209 ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx) 210 211 if ctx.HasError() { 212 ctx.HTML(http.StatusOK, tplSignIn) 213 return 214 } 215 216 form := web.GetForm(ctx).(*forms.SignInForm) 217 218 if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin { 219 context.SetCaptchaData(ctx) 220 221 context.VerifyCaptcha(ctx, tplSignIn, form) 222 if ctx.Written() { 223 return 224 } 225 } 226 227 u, source, err := auth_service.UserSignIn(ctx, form.UserName, form.Password) 228 if err != nil { 229 if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) { 230 ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form) 231 log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err) 232 } else if user_model.IsErrEmailAlreadyUsed(err) { 233 ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSignIn, &form) 234 log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err) 235 } else if user_model.IsErrUserProhibitLogin(err) { 236 log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err) 237 ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") 238 ctx.HTML(http.StatusOK, "user/auth/prohibit_login") 239 } else if user_model.IsErrUserInactive(err) { 240 if setting.Service.RegisterEmailConfirm { 241 ctx.Data["Title"] = ctx.Tr("auth.active_your_account") 242 ctx.HTML(http.StatusOK, TplActivate) 243 } else { 244 log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err) 245 ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") 246 ctx.HTML(http.StatusOK, "user/auth/prohibit_login") 247 } 248 } else { 249 ctx.ServerError("UserSignIn", err) 250 } 251 return 252 } 253 254 // Now handle 2FA: 255 256 // First of all if the source can skip local two fa we're done 257 if skipper, ok := source.Cfg.(auth_service.LocalTwoFASkipper); ok && skipper.IsSkipLocalTwoFA() { 258 handleSignIn(ctx, u, form.Remember) 259 return 260 } 261 262 // If this user is enrolled in 2FA TOTP, we can't sign the user in just yet. 263 // Instead, redirect them to the 2FA authentication page. 264 hasTOTPtwofa, err := auth.HasTwoFactorByUID(ctx, u.ID) 265 if err != nil { 266 ctx.ServerError("UserSignIn", err) 267 return 268 } 269 270 // Check if the user has webauthn registration 271 hasWebAuthnTwofa, err := auth.HasWebAuthnRegistrationsByUID(ctx, u.ID) 272 if err != nil { 273 ctx.ServerError("UserSignIn", err) 274 return 275 } 276 277 if !hasTOTPtwofa && !hasWebAuthnTwofa { 278 // No two factor auth configured we can sign in the user 279 handleSignIn(ctx, u, form.Remember) 280 return 281 } 282 283 updates := map[string]any{ 284 // User will need to use 2FA TOTP or WebAuthn, save data 285 "twofaUid": u.ID, 286 "twofaRemember": form.Remember, 287 } 288 if hasTOTPtwofa { 289 // User will need to use WebAuthn, save data 290 updates["totpEnrolled"] = u.ID 291 } 292 if err := updateSession(ctx, nil, updates); err != nil { 293 ctx.ServerError("UserSignIn: Unable to update session", err) 294 return 295 } 296 297 // If we have WebAuthn redirect there first 298 if hasWebAuthnTwofa { 299 ctx.Redirect(setting.AppSubURL + "/user/webauthn") 300 return 301 } 302 303 // Fallback to 2FA 304 ctx.Redirect(setting.AppSubURL + "/user/two_factor") 305 } 306 307 // This handles the final part of the sign-in process of the user. 308 func handleSignIn(ctx *context.Context, u *user_model.User, remember bool) { 309 redirect := handleSignInFull(ctx, u, remember, true) 310 if ctx.Written() { 311 return 312 } 313 ctx.Redirect(redirect) 314 } 315 316 func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRedirect bool) string { 317 if remember { 318 nt, token, err := auth_service.CreateAuthTokenForUserID(ctx, u.ID) 319 if err != nil { 320 ctx.ServerError("CreateAuthTokenForUserID", err) 321 return setting.AppSubURL + "/" 322 } 323 324 ctx.SetSiteCookie(setting.CookieRememberName, nt.ID+":"+token, setting.LogInRememberDays*timeutil.Day) 325 } 326 327 if err := updateSession(ctx, []string{ 328 // Delete the openid, 2fa and linkaccount data 329 "openid_verified_uri", 330 "openid_signin_remember", 331 "openid_determined_email", 332 "openid_determined_username", 333 "twofaUid", 334 "twofaRemember", 335 "linkAccount", 336 }, map[string]any{ 337 "uid": u.ID, 338 "uname": u.Name, 339 }); err != nil { 340 ctx.ServerError("RegenerateSession", err) 341 return setting.AppSubURL + "/" 342 } 343 344 // Language setting of the user overwrites the one previously set 345 // If the user does not have a locale set, we save the current one. 346 if u.Language == "" { 347 opts := &user_service.UpdateOptions{ 348 Language: optional.Some(ctx.Locale.Language()), 349 } 350 if err := user_service.UpdateUser(ctx, u, opts); err != nil { 351 ctx.ServerError("UpdateUser Language", fmt.Errorf("Error updating user language [user: %d, locale: %s]", u.ID, ctx.Locale.Language())) 352 return setting.AppSubURL + "/" 353 } 354 } 355 356 middleware.SetLocaleCookie(ctx.Resp, u.Language, 0) 357 358 if ctx.Locale.Language() != u.Language { 359 ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req) 360 } 361 362 // Clear whatever CSRF cookie has right now, force to generate a new one 363 ctx.Csrf.DeleteCookie(ctx) 364 365 // Register last login 366 if err := user_service.UpdateUser(ctx, u, &user_service.UpdateOptions{SetLastLogin: true}); err != nil { 367 ctx.ServerError("UpdateUser", err) 368 return setting.AppSubURL + "/" 369 } 370 371 if redirectTo := ctx.GetSiteCookie("redirect_to"); redirectTo != "" && httplib.IsCurrentGiteaSiteURL(ctx, redirectTo) { 372 middleware.DeleteRedirectToCookie(ctx.Resp) 373 if obeyRedirect { 374 ctx.RedirectToCurrentSite(redirectTo) 375 } 376 return redirectTo 377 } 378 379 if obeyRedirect { 380 ctx.Redirect(setting.AppSubURL + "/") 381 } 382 return setting.AppSubURL + "/" 383 } 384 385 // extractUserNameFromOAuth2 tries to extract a normalized username from the given OAuth2 user. 386 // It returns ("", nil) if the required field doesn't exist. 387 func extractUserNameFromOAuth2(gothUser *goth.User) (string, error) { 388 switch setting.OAuth2Client.Username { 389 case setting.OAuth2UsernameEmail: 390 return user_model.NormalizeUserName(gothUser.Email) 391 case setting.OAuth2UsernamePreferredUsername: 392 if preferredUsername, ok := gothUser.RawData["preferred_username"].(string); ok { 393 return user_model.NormalizeUserName(preferredUsername) 394 } 395 return "", nil 396 case setting.OAuth2UsernameNickname: 397 return user_model.NormalizeUserName(gothUser.NickName) 398 default: // OAuth2UsernameUserid 399 return gothUser.UserID, nil 400 } 401 } 402 403 // HandleSignOut resets the session and sets the cookies 404 func HandleSignOut(ctx *context.Context) { 405 _ = ctx.Session.Flush() 406 _ = ctx.Session.Destroy(ctx.Resp, ctx.Req) 407 ctx.DeleteSiteCookie(setting.CookieRememberName) 408 ctx.Csrf.DeleteCookie(ctx) 409 middleware.DeleteRedirectToCookie(ctx.Resp) 410 } 411 412 // SignOut sign out from login status 413 func SignOut(ctx *context.Context) { 414 if ctx.Doer != nil { 415 eventsource.GetManager().SendMessageBlocking(ctx.Doer.ID, &eventsource.Event{ 416 Name: "logout", 417 Data: ctx.Session.ID(), 418 }) 419 } 420 HandleSignOut(ctx) 421 ctx.JSONRedirect(setting.AppSubURL + "/") 422 } 423 424 // SignUp render the register page 425 func SignUp(ctx *context.Context) { 426 ctx.Data["Title"] = ctx.Tr("sign_up") 427 428 ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up" 429 430 oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true)) 431 if err != nil { 432 ctx.ServerError("UserSignUp", err) 433 return 434 } 435 436 ctx.Data["OAuth2Providers"] = oauth2Providers 437 context.SetCaptchaData(ctx) 438 439 ctx.Data["PageIsSignUp"] = true 440 441 // Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true 442 ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration || setting.Service.AllowOnlyExternalRegistration 443 444 redirectTo := ctx.FormString("redirect_to") 445 if len(redirectTo) > 0 { 446 middleware.SetRedirectToCookie(ctx.Resp, redirectTo) 447 } 448 449 ctx.HTML(http.StatusOK, tplSignUp) 450 } 451 452 // SignUpPost response for sign up information submission 453 func SignUpPost(ctx *context.Context) { 454 form := web.GetForm(ctx).(*forms.RegisterForm) 455 ctx.Data["Title"] = ctx.Tr("sign_up") 456 457 ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up" 458 459 oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true)) 460 if err != nil { 461 ctx.ServerError("UserSignUp", err) 462 return 463 } 464 465 ctx.Data["OAuth2Providers"] = oauth2Providers 466 context.SetCaptchaData(ctx) 467 468 ctx.Data["PageIsSignUp"] = true 469 470 // Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true 471 if setting.Service.DisableRegistration || setting.Service.AllowOnlyExternalRegistration { 472 ctx.Error(http.StatusForbidden) 473 return 474 } 475 476 if ctx.HasError() { 477 ctx.HTML(http.StatusOK, tplSignUp) 478 return 479 } 480 481 context.VerifyCaptcha(ctx, tplSignUp, form) 482 if ctx.Written() { 483 return 484 } 485 486 if !form.IsEmailDomainAllowed() { 487 ctx.RenderWithErr(ctx.Tr("auth.email_domain_blacklisted"), tplSignUp, &form) 488 return 489 } 490 491 if form.Password != form.Retype { 492 ctx.Data["Err_Password"] = true 493 ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplSignUp, &form) 494 return 495 } 496 if len(form.Password) < setting.MinPasswordLength { 497 ctx.Data["Err_Password"] = true 498 ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplSignUp, &form) 499 return 500 } 501 if !password.IsComplexEnough(form.Password) { 502 ctx.Data["Err_Password"] = true 503 ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplSignUp, &form) 504 return 505 } 506 if err := password.IsPwned(ctx, form.Password); err != nil { 507 errMsg := ctx.Tr("auth.password_pwned") 508 if password.IsErrIsPwnedRequest(err) { 509 log.Error(err.Error()) 510 errMsg = ctx.Tr("auth.password_pwned_err") 511 } 512 ctx.Data["Err_Password"] = true 513 ctx.RenderWithErr(errMsg, tplSignUp, &form) 514 return 515 } 516 517 u := &user_model.User{ 518 Name: form.UserName, 519 Email: form.Email, 520 Passwd: form.Password, 521 } 522 523 if !createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil, nil, false) { 524 // error already handled 525 return 526 } 527 528 ctx.Flash.Success(ctx.Tr("auth.sign_up_successful")) 529 handleSignIn(ctx, u, false) 530 } 531 532 // createAndHandleCreatedUser calls createUserInContext and 533 // then handleUserCreated. 534 func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) bool { 535 if !createUserInContext(ctx, tpl, form, u, overwrites, gothUser, allowLink) { 536 return false 537 } 538 return handleUserCreated(ctx, u, gothUser) 539 } 540 541 // createUserInContext creates a user and handles errors within a given context. 542 // Optionally a template can be specified. 543 func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) (ok bool) { 544 if err := user_model.CreateUser(ctx, u, overwrites); err != nil { 545 if allowLink && (user_model.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) { 546 if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto { 547 var user *user_model.User 548 user = &user_model.User{Name: u.Name} 549 hasUser, err := user_model.GetUser(ctx, user) 550 if !hasUser || err != nil { 551 user = &user_model.User{Email: u.Email} 552 hasUser, err = user_model.GetUser(ctx, user) 553 if !hasUser || err != nil { 554 ctx.ServerError("UserLinkAccount", err) 555 return false 556 } 557 } 558 559 // TODO: probably we should respect 'remember' user's choice... 560 linkAccount(ctx, user, *gothUser, true) 561 return false // user is already created here, all redirects are handled 562 } else if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingLogin { 563 showLinkingLogin(ctx, *gothUser) 564 return false // user will be created only after linking login 565 } 566 } 567 568 // handle error without template 569 if len(tpl) == 0 { 570 ctx.ServerError("CreateUser", err) 571 return false 572 } 573 574 // handle error with template 575 switch { 576 case user_model.IsErrUserAlreadyExist(err): 577 ctx.Data["Err_UserName"] = true 578 ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tpl, form) 579 case user_model.IsErrEmailAlreadyUsed(err): 580 ctx.Data["Err_Email"] = true 581 ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tpl, form) 582 case user_model.IsErrEmailCharIsNotSupported(err): 583 ctx.Data["Err_Email"] = true 584 ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form) 585 case user_model.IsErrEmailInvalid(err): 586 ctx.Data["Err_Email"] = true 587 ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form) 588 case db.IsErrNameReserved(err): 589 ctx.Data["Err_UserName"] = true 590 ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(db.ErrNameReserved).Name), tpl, form) 591 case db.IsErrNamePatternNotAllowed(err): 592 ctx.Data["Err_UserName"] = true 593 ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tpl, form) 594 case db.IsErrNameCharsNotAllowed(err): 595 ctx.Data["Err_UserName"] = true 596 ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", err.(db.ErrNameCharsNotAllowed).Name), tpl, form) 597 default: 598 ctx.ServerError("CreateUser", err) 599 } 600 return false 601 } 602 log.Trace("Account created: %s", u.Name) 603 return true 604 } 605 606 // handleUserCreated does additional steps after a new user is created. 607 // It auto-sets admin for the only user, updates the optional external user and 608 // sends a confirmation email if required. 609 func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) { 610 // Auto-set admin for the only user. 611 if user_model.CountUsers(ctx, nil) == 1 { 612 opts := &user_service.UpdateOptions{ 613 IsActive: optional.Some(true), 614 IsAdmin: optional.Some(true), 615 SetLastLogin: true, 616 } 617 if err := user_service.UpdateUser(ctx, u, opts); err != nil { 618 ctx.ServerError("UpdateUser", err) 619 return false 620 } 621 } 622 623 // update external user information 624 if gothUser != nil { 625 if err := externalaccount.UpdateExternalUser(ctx, u, *gothUser); err != nil { 626 if !errors.Is(err, util.ErrNotExist) { 627 log.Error("UpdateExternalUser failed: %v", err) 628 } 629 } 630 } 631 632 // for active user or the first (admin) user, we don't need to send confirmation email 633 if u.IsActive || u.ID == 1 { 634 return true 635 } 636 637 if setting.Service.RegisterManualConfirm { 638 renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.manual_activation_only")) 639 return false 640 } 641 642 sendActivateEmail(ctx, u) 643 return false 644 } 645 646 func renderActivationPromptMessage(ctx *context.Context, msg template.HTML) { 647 ctx.Data["ActivationPromptMessage"] = msg 648 ctx.HTML(http.StatusOK, TplActivatePrompt) 649 } 650 651 func sendActivateEmail(ctx *context.Context, u *user_model.User) { 652 if ctx.Cache.IsExist("MailResendLimit_" + u.LowerName) { 653 renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.resent_limit_prompt")) 654 return 655 } 656 657 if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { 658 log.Error("Set cache(MailResendLimit) fail: %v", err) 659 renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.resent_limit_prompt")) 660 return 661 } 662 663 mailer.SendActivateAccountMail(ctx.Locale, u) 664 665 activeCodeLives := timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale) 666 msgHTML := ctx.Locale.Tr("auth.confirmation_mail_sent_prompt_ex", u.Email, activeCodeLives) 667 renderActivationPromptMessage(ctx, msgHTML) 668 } 669 670 func renderActivationVerifyPassword(ctx *context.Context, code string) { 671 ctx.Data["ActivationCode"] = code 672 ctx.Data["NeedVerifyLocalPassword"] = true 673 ctx.HTML(http.StatusOK, TplActivate) 674 } 675 676 func renderActivationChangeEmail(ctx *context.Context) { 677 ctx.HTML(http.StatusOK, TplActivate) 678 } 679 680 // Activate render activate user page 681 func Activate(ctx *context.Context) { 682 code := ctx.FormString("code") 683 684 if code == "" { 685 if ctx.Doer == nil { 686 ctx.Redirect(setting.AppSubURL + "/user/login") 687 return 688 } else if ctx.Doer.IsActive { 689 ctx.Redirect(setting.AppSubURL + "/") 690 return 691 } 692 693 if setting.MailService == nil || !setting.Service.RegisterEmailConfirm { 694 renderActivationPromptMessage(ctx, ctx.Tr("auth.disable_register_mail")) 695 return 696 } 697 698 // Resend confirmation email. FIXME: ideally this should be in a POST request 699 sendActivateEmail(ctx, ctx.Doer) 700 return 701 } 702 703 // TODO: ctx.Doer/ctx.Data["SignedUser"] could be nil or not the same user as the one being activated 704 user := user_model.VerifyUserActiveCode(ctx, code) 705 if user == nil { // if code is wrong 706 renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.invalid_code")) 707 return 708 } 709 710 // if account is local account, verify password 711 if user.LoginSource == 0 { 712 renderActivationVerifyPassword(ctx, code) 713 return 714 } 715 716 handleAccountActivation(ctx, user) 717 } 718 719 // ActivatePost handles account activation with password check 720 func ActivatePost(ctx *context.Context) { 721 code := ctx.FormString("code") 722 if ctx.Doer != nil && ctx.Doer.IsActive { 723 ctx.Redirect(setting.AppSubURL + "/user/activate") // it will redirect again to the correct page 724 return 725 } 726 727 if code == "" { 728 newEmail := strings.TrimSpace(ctx.FormString("change_email")) 729 if ctx.Doer != nil && newEmail != "" && !strings.EqualFold(ctx.Doer.Email, newEmail) { 730 if user_model.ValidateEmail(newEmail) != nil { 731 ctx.Flash.Error(ctx.Locale.Tr("form.email_invalid"), true) 732 renderActivationChangeEmail(ctx) 733 return 734 } 735 err := user_model.ChangeInactivePrimaryEmail(ctx, ctx.Doer.ID, ctx.Doer.Email, newEmail) 736 if err != nil { 737 ctx.Flash.Error(ctx.Locale.Tr("admin.emails.not_updated", newEmail), true) 738 renderActivationChangeEmail(ctx) 739 return 740 } 741 ctx.Doer.Email = newEmail 742 } 743 // FIXME: at the moment, GET request handles the "send confirmation email" action. But the old code does this redirect and then send a confirmation email. 744 ctx.Redirect(setting.AppSubURL + "/user/activate") 745 return 746 } 747 748 // TODO: ctx.Doer/ctx.Data["SignedUser"] could be nil or not the same user as the one being activated 749 user := user_model.VerifyUserActiveCode(ctx, code) 750 if user == nil { // if code is wrong 751 renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.invalid_code")) 752 return 753 } 754 755 // if account is local account, verify password 756 if user.LoginSource == 0 { 757 password := ctx.FormString("password") 758 if password == "" { 759 renderActivationVerifyPassword(ctx, code) 760 return 761 } 762 if !user.ValidatePassword(password) { 763 ctx.Flash.Error(ctx.Locale.Tr("auth.invalid_password"), true) 764 renderActivationVerifyPassword(ctx, code) 765 return 766 } 767 } 768 769 handleAccountActivation(ctx, user) 770 } 771 772 func handleAccountActivation(ctx *context.Context, user *user_model.User) { 773 user.IsActive = true 774 var err error 775 if user.Rands, err = user_model.GetUserSalt(); err != nil { 776 ctx.ServerError("UpdateUser", err) 777 return 778 } 779 if err := user_model.UpdateUserCols(ctx, user, "is_active", "rands"); err != nil { 780 if user_model.IsErrUserNotExist(err) { 781 ctx.NotFound("UpdateUserCols", err) 782 } else { 783 ctx.ServerError("UpdateUser", err) 784 } 785 return 786 } 787 788 if err := user_model.ActivateUserEmail(ctx, user.ID, user.Email, true); err != nil { 789 log.Error("Unable to activate email for user: %-v with email: %s: %v", user, user.Email, err) 790 ctx.ServerError("ActivateUserEmail", err) 791 return 792 } 793 794 log.Trace("User activated: %s", user.Name) 795 796 if err := updateSession(ctx, nil, map[string]any{ 797 "uid": user.ID, 798 "uname": user.Name, 799 }); err != nil { 800 log.Error("Unable to regenerate session for user: %-v with email: %s: %v", user, user.Email, err) 801 ctx.ServerError("ActivateUserEmail", err) 802 return 803 } 804 805 if err := resetLocale(ctx, user); err != nil { 806 ctx.ServerError("resetLocale", err) 807 return 808 } 809 810 if err := user_service.UpdateUser(ctx, user, &user_service.UpdateOptions{SetLastLogin: true}); err != nil { 811 ctx.ServerError("UpdateUser", err) 812 return 813 } 814 815 ctx.Flash.Success(ctx.Tr("auth.account_activated")) 816 if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 { 817 middleware.DeleteRedirectToCookie(ctx.Resp) 818 ctx.RedirectToCurrentSite(redirectTo) 819 return 820 } 821 822 ctx.Redirect(setting.AppSubURL + "/") 823 } 824 825 // ActivateEmail render the activate email page 826 func ActivateEmail(ctx *context.Context) { 827 code := ctx.FormString("code") 828 emailStr := ctx.FormString("email") 829 830 // Verify code. 831 if email := user_model.VerifyActiveEmailCode(ctx, code, emailStr); email != nil { 832 if err := user_model.ActivateEmail(ctx, email); err != nil { 833 ctx.ServerError("ActivateEmail", err) 834 return 835 } 836 837 log.Trace("Email activated: %s", email.Email) 838 ctx.Flash.Success(ctx.Tr("settings.add_email_success")) 839 840 if u, err := user_model.GetUserByID(ctx, email.UID); err != nil { 841 log.Warn("GetUserByID: %d", email.UID) 842 } else { 843 // Allow user to validate more emails 844 _ = ctx.Cache.Delete("MailResendLimit_" + u.LowerName) 845 } 846 } 847 848 // FIXME: e-mail verification does not require the user to be logged in, 849 // so this could be redirecting to the login page. 850 // Should users be logged in automatically here? (consider 2FA requirements, etc.) 851 ctx.Redirect(setting.AppSubURL + "/user/settings/account") 852 } 853 854 func updateSession(ctx *context.Context, deletes []string, updates map[string]any) error { 855 if _, err := session.RegenerateSession(ctx.Resp, ctx.Req); err != nil { 856 return fmt.Errorf("regenerate session: %w", err) 857 } 858 sess := ctx.Session 859 sessID := sess.ID() 860 for _, k := range deletes { 861 if err := sess.Delete(k); err != nil { 862 return fmt.Errorf("delete %v in session[%s]: %w", k, sessID, err) 863 } 864 } 865 for k, v := range updates { 866 if err := sess.Set(k, v); err != nil { 867 return fmt.Errorf("set %v in session[%s]: %w", k, sessID, err) 868 } 869 } 870 if err := sess.Release(); err != nil { 871 return fmt.Errorf("store session[%s]: %w", sessID, err) 872 } 873 return nil 874 }