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.

u2f.go 3.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package auth
  5. import (
  6. "errors"
  7. "net/http"
  8. "code.gitea.io/gitea/models/auth"
  9. user_model "code.gitea.io/gitea/models/user"
  10. "code.gitea.io/gitea/modules/base"
  11. "code.gitea.io/gitea/modules/context"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/setting"
  14. "code.gitea.io/gitea/modules/web"
  15. "code.gitea.io/gitea/services/externalaccount"
  16. "github.com/tstranex/u2f"
  17. )
  18. var tplU2F base.TplName = "user/auth/u2f"
  19. // U2F shows the U2F login page
  20. func U2F(ctx *context.Context) {
  21. ctx.Data["Title"] = ctx.Tr("twofa")
  22. ctx.Data["RequireU2F"] = true
  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 U2F session"))
  30. return
  31. }
  32. // See whether TOTP is also available.
  33. if ctx.Session.Get("totpEnrolled") != nil {
  34. ctx.Data["TOTPEnrolled"] = true
  35. }
  36. ctx.HTML(http.StatusOK, tplU2F)
  37. }
  38. // U2FChallenge submits a sign challenge to the browser
  39. func U2FChallenge(ctx *context.Context) {
  40. // Ensure user is in a U2F session.
  41. idSess := ctx.Session.Get("twofaUid")
  42. if idSess == nil {
  43. ctx.ServerError("UserSignIn", errors.New("not in U2F session"))
  44. return
  45. }
  46. id := idSess.(int64)
  47. regs, err := auth.GetU2FRegistrationsByUID(id)
  48. if err != nil {
  49. ctx.ServerError("UserSignIn", err)
  50. return
  51. }
  52. if len(regs) == 0 {
  53. ctx.ServerError("UserSignIn", errors.New("no device registered"))
  54. return
  55. }
  56. challenge, err := u2f.NewChallenge(setting.U2F.AppID, setting.U2F.TrustedFacets)
  57. if err != nil {
  58. ctx.ServerError("u2f.NewChallenge", err)
  59. return
  60. }
  61. if err := ctx.Session.Set("u2fChallenge", challenge); err != nil {
  62. ctx.ServerError("UserSignIn: unable to set u2fChallenge in session", err)
  63. return
  64. }
  65. if err := ctx.Session.Release(); err != nil {
  66. ctx.ServerError("UserSignIn: unable to store session", err)
  67. }
  68. ctx.JSON(http.StatusOK, challenge.SignRequest(regs.ToRegistrations()))
  69. }
  70. // U2FSign authenticates the user by signResp
  71. func U2FSign(ctx *context.Context) {
  72. signResp := web.GetForm(ctx).(*u2f.SignResponse)
  73. challSess := ctx.Session.Get("u2fChallenge")
  74. idSess := ctx.Session.Get("twofaUid")
  75. if challSess == nil || idSess == nil {
  76. ctx.ServerError("UserSignIn", errors.New("not in U2F session"))
  77. return
  78. }
  79. challenge := challSess.(*u2f.Challenge)
  80. id := idSess.(int64)
  81. regs, err := auth.GetU2FRegistrationsByUID(id)
  82. if err != nil {
  83. ctx.ServerError("UserSignIn", err)
  84. return
  85. }
  86. for _, reg := range regs {
  87. r, err := reg.Parse()
  88. if err != nil {
  89. log.Error("parsing u2f registration: %v", err)
  90. continue
  91. }
  92. newCounter, authErr := r.Authenticate(*signResp, *challenge, reg.Counter)
  93. if authErr == nil {
  94. reg.Counter = newCounter
  95. user, err := user_model.GetUserByID(id)
  96. if err != nil {
  97. ctx.ServerError("UserSignIn", err)
  98. return
  99. }
  100. remember := ctx.Session.Get("twofaRemember").(bool)
  101. if err := reg.UpdateCounter(); err != nil {
  102. ctx.ServerError("UserSignIn", err)
  103. return
  104. }
  105. if ctx.Session.Get("linkAccount") != nil {
  106. if err := externalaccount.LinkAccountFromStore(ctx.Session, user); err != nil {
  107. ctx.ServerError("UserSignIn", err)
  108. return
  109. }
  110. }
  111. redirect := handleSignInFull(ctx, user, remember, false)
  112. if ctx.Written() {
  113. return
  114. }
  115. if redirect == "" {
  116. redirect = setting.AppSubURL + "/"
  117. }
  118. ctx.PlainText(http.StatusOK, redirect)
  119. return
  120. }
  121. }
  122. ctx.Error(http.StatusUnauthorized)
  123. }