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