You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package auth
  4. import (
  5. "errors"
  6. "net/http"
  7. "code.gitea.io/gitea/models/auth"
  8. user_model "code.gitea.io/gitea/models/user"
  9. "code.gitea.io/gitea/modules/base"
  10. "code.gitea.io/gitea/modules/context"
  11. "code.gitea.io/gitea/modules/setting"
  12. "code.gitea.io/gitea/modules/web"
  13. "code.gitea.io/gitea/services/externalaccount"
  14. "code.gitea.io/gitea/services/forms"
  15. )
  16. var (
  17. tplTwofa base.TplName = "user/auth/twofa"
  18. tplTwofaScratch base.TplName = "user/auth/twofa_scratch"
  19. )
  20. // TwoFactor shows the user a two-factor authentication page.
  21. func TwoFactor(ctx *context.Context) {
  22. ctx.Data["Title"] = ctx.Tr("twofa")
  23. // Check auto-login.
  24. if checkAutoLogin(ctx) {
  25. return
  26. }
  27. // Ensure user is in a 2FA session.
  28. if ctx.Session.Get("twofaUid") == nil {
  29. ctx.ServerError("UserSignIn", errors.New("not in 2FA session"))
  30. return
  31. }
  32. ctx.HTML(http.StatusOK, tplTwofa)
  33. }
  34. // TwoFactorPost validates a user's two-factor authentication token.
  35. func TwoFactorPost(ctx *context.Context) {
  36. form := web.GetForm(ctx).(*forms.TwoFactorAuthForm)
  37. ctx.Data["Title"] = ctx.Tr("twofa")
  38. // Ensure user is in a 2FA session.
  39. idSess := ctx.Session.Get("twofaUid")
  40. if idSess == nil {
  41. ctx.ServerError("UserSignIn", errors.New("not in 2FA session"))
  42. return
  43. }
  44. id := idSess.(int64)
  45. twofa, err := auth.GetTwoFactorByUID(ctx, id)
  46. if err != nil {
  47. ctx.ServerError("UserSignIn", err)
  48. return
  49. }
  50. // Validate the passcode with the stored TOTP secret.
  51. ok, err := twofa.ValidateTOTP(form.Passcode)
  52. if err != nil {
  53. ctx.ServerError("UserSignIn", err)
  54. return
  55. }
  56. if ok && twofa.LastUsedPasscode != form.Passcode {
  57. remember := ctx.Session.Get("twofaRemember").(bool)
  58. u, err := user_model.GetUserByID(ctx, id)
  59. if err != nil {
  60. ctx.ServerError("UserSignIn", err)
  61. return
  62. }
  63. if ctx.Session.Get("linkAccount") != nil {
  64. err = externalaccount.LinkAccountFromStore(ctx, ctx.Session, u)
  65. if err != nil {
  66. ctx.ServerError("UserSignIn", err)
  67. return
  68. }
  69. }
  70. twofa.LastUsedPasscode = form.Passcode
  71. if err = auth.UpdateTwoFactor(ctx, twofa); err != nil {
  72. ctx.ServerError("UserSignIn", err)
  73. return
  74. }
  75. handleSignIn(ctx, u, remember)
  76. return
  77. }
  78. ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplTwofa, forms.TwoFactorAuthForm{})
  79. }
  80. // TwoFactorScratch shows the scratch code form for two-factor authentication.
  81. func TwoFactorScratch(ctx *context.Context) {
  82. ctx.Data["Title"] = ctx.Tr("twofa_scratch")
  83. // Check auto-login.
  84. if checkAutoLogin(ctx) {
  85. return
  86. }
  87. // Ensure user is in a 2FA session.
  88. if ctx.Session.Get("twofaUid") == nil {
  89. ctx.ServerError("UserSignIn", errors.New("not in 2FA session"))
  90. return
  91. }
  92. ctx.HTML(http.StatusOK, tplTwofaScratch)
  93. }
  94. // TwoFactorScratchPost validates and invalidates a user's two-factor scratch token.
  95. func TwoFactorScratchPost(ctx *context.Context) {
  96. form := web.GetForm(ctx).(*forms.TwoFactorScratchAuthForm)
  97. ctx.Data["Title"] = ctx.Tr("twofa_scratch")
  98. // Ensure user is in a 2FA session.
  99. idSess := ctx.Session.Get("twofaUid")
  100. if idSess == nil {
  101. ctx.ServerError("UserSignIn", errors.New("not in 2FA session"))
  102. return
  103. }
  104. id := idSess.(int64)
  105. twofa, err := auth.GetTwoFactorByUID(ctx, id)
  106. if err != nil {
  107. ctx.ServerError("UserSignIn", err)
  108. return
  109. }
  110. // Validate the passcode with the stored TOTP secret.
  111. if twofa.VerifyScratchToken(form.Token) {
  112. // Invalidate the scratch token.
  113. _, err = twofa.GenerateScratchToken()
  114. if err != nil {
  115. ctx.ServerError("UserSignIn", err)
  116. return
  117. }
  118. if err = auth.UpdateTwoFactor(ctx, twofa); err != nil {
  119. ctx.ServerError("UserSignIn", err)
  120. return
  121. }
  122. remember := ctx.Session.Get("twofaRemember").(bool)
  123. u, err := user_model.GetUserByID(ctx, id)
  124. if err != nil {
  125. ctx.ServerError("UserSignIn", err)
  126. return
  127. }
  128. handleSignInFull(ctx, u, remember, false)
  129. if ctx.Written() {
  130. return
  131. }
  132. ctx.Flash.Info(ctx.Tr("auth.twofa_scratch_used"))
  133. ctx.Redirect(setting.AppSubURL + "/user/settings/security")
  134. return
  135. }
  136. ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplTwofaScratch, forms.TwoFactorScratchAuthForm{})
  137. }