two_factor.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. package models
  2. import (
  3. "encoding/base64"
  4. "fmt"
  5. "gitote/gitote/models/errors"
  6. "gitote/gitote/pkg/setting"
  7. "gitote/gitote/pkg/tool"
  8. "strings"
  9. "time"
  10. "github.com/Unknwon/com"
  11. "github.com/go-xorm/xorm"
  12. "github.com/pquerna/otp/totp"
  13. log "gopkg.in/clog.v1"
  14. )
  15. // TwoFactor represents a two-factor authentication token.
  16. type TwoFactor struct {
  17. ID int64
  18. UserID int64 `xorm:"UNIQUE"`
  19. Secret string
  20. Created time.Time `xorm:"-" json:"-"`
  21. CreatedUnix int64
  22. }
  23. func (t *TwoFactor) BeforeInsert() {
  24. t.CreatedUnix = time.Now().Unix()
  25. }
  26. func (t *TwoFactor) AfterSet(colName string, _ xorm.Cell) {
  27. switch colName {
  28. case "created_unix":
  29. t.Created = time.Unix(t.CreatedUnix, 0).Local()
  30. }
  31. }
  32. // ValidateTOTP returns true if given passcode is valid for two-factor authentication token.
  33. // It also returns possible validation error.
  34. func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) {
  35. secret, err := base64.StdEncoding.DecodeString(t.Secret)
  36. if err != nil {
  37. return false, fmt.Errorf("DecodeString: %v", err)
  38. }
  39. decryptSecret, err := com.AESGCMDecrypt(tool.MD5Bytes(setting.SecretKey), secret)
  40. if err != nil {
  41. return false, fmt.Errorf("AESGCMDecrypt: %v", err)
  42. }
  43. return totp.Validate(passcode, string(decryptSecret)), nil
  44. }
  45. // IsUserEnabledTwoFactor returns true if user has enabled two-factor authentication.
  46. func IsUserEnabledTwoFactor(userID int64) bool {
  47. has, err := x.Where("user_id = ?", userID).Get(new(TwoFactor))
  48. if err != nil {
  49. log.Error(2, "IsUserEnabledTwoFactor [user_id: %d]: %v", userID, err)
  50. }
  51. return has
  52. }
  53. func generateRecoveryCodes(userID int64) ([]*TwoFactorRecoveryCode, error) {
  54. recoveryCodes := make([]*TwoFactorRecoveryCode, 10)
  55. for i := 0; i < 10; i++ {
  56. code, err := tool.RandomString(10)
  57. if err != nil {
  58. return nil, fmt.Errorf("RandomString: %v", err)
  59. }
  60. recoveryCodes[i] = &TwoFactorRecoveryCode{
  61. UserID: userID,
  62. Code: strings.ToLower(code[:5] + "-" + code[5:]),
  63. }
  64. }
  65. return recoveryCodes, nil
  66. }
  67. // NewTwoFactor creates a new two-factor authentication token and recovery codes for given user.
  68. func NewTwoFactor(userID int64, secret string) error {
  69. t := &TwoFactor{
  70. UserID: userID,
  71. }
  72. // Encrypt secret
  73. encryptSecret, err := com.AESGCMEncrypt(tool.MD5Bytes(setting.SecretKey), []byte(secret))
  74. if err != nil {
  75. return fmt.Errorf("AESGCMEncrypt: %v", err)
  76. }
  77. t.Secret = base64.StdEncoding.EncodeToString(encryptSecret)
  78. recoveryCodes, err := generateRecoveryCodes(userID)
  79. if err != nil {
  80. return fmt.Errorf("generateRecoveryCodes: %v", err)
  81. }
  82. sess := x.NewSession()
  83. defer sess.Close()
  84. if err = sess.Begin(); err != nil {
  85. return err
  86. }
  87. if _, err = sess.Insert(t); err != nil {
  88. return fmt.Errorf("insert two-factor: %v", err)
  89. } else if _, err = sess.Insert(recoveryCodes); err != nil {
  90. return fmt.Errorf("insert recovery codes: %v", err)
  91. }
  92. return sess.Commit()
  93. }
  94. // GetTwoFactorByUserID returns two-factor authentication token of given user.
  95. func GetTwoFactorByUserID(userID int64) (*TwoFactor, error) {
  96. t := new(TwoFactor)
  97. has, err := x.Where("user_id = ?", userID).Get(t)
  98. if err != nil {
  99. return nil, err
  100. } else if !has {
  101. return nil, errors.TwoFactorNotFound{userID}
  102. }
  103. return t, nil
  104. }
  105. // DeleteTwoFactor removes two-factor authentication token and recovery codes of given user.
  106. func DeleteTwoFactor(userID int64) (err error) {
  107. sess := x.NewSession()
  108. defer sess.Close()
  109. if err = sess.Begin(); err != nil {
  110. return err
  111. }
  112. if _, err = sess.Where("user_id = ?", userID).Delete(new(TwoFactor)); err != nil {
  113. return fmt.Errorf("delete two-factor: %v", err)
  114. } else if err = deleteRecoveryCodesByUserID(sess, userID); err != nil {
  115. return fmt.Errorf("deleteRecoveryCodesByUserID: %v", err)
  116. }
  117. return sess.Commit()
  118. }
  119. // TwoFactorRecoveryCode represents a two-factor authentication recovery code.
  120. type TwoFactorRecoveryCode struct {
  121. ID int64
  122. UserID int64
  123. Code string `xorm:"VARCHAR(11)"`
  124. IsUsed bool
  125. }
  126. // GetRecoveryCodesByUserID returns all recovery codes of given user.
  127. func GetRecoveryCodesByUserID(userID int64) ([]*TwoFactorRecoveryCode, error) {
  128. recoveryCodes := make([]*TwoFactorRecoveryCode, 0, 10)
  129. return recoveryCodes, x.Where("user_id = ?", userID).Find(&recoveryCodes)
  130. }
  131. func deleteRecoveryCodesByUserID(e Engine, userID int64) error {
  132. _, err := e.Where("user_id = ?", userID).Delete(new(TwoFactorRecoveryCode))
  133. return err
  134. }
  135. // RegenerateRecoveryCodes regenerates new set of recovery codes for given user.
  136. func RegenerateRecoveryCodes(userID int64) error {
  137. recoveryCodes, err := generateRecoveryCodes(userID)
  138. if err != nil {
  139. return fmt.Errorf("generateRecoveryCodes: %v", err)
  140. }
  141. sess := x.NewSession()
  142. defer sess.Close()
  143. if err = sess.Begin(); err != nil {
  144. return err
  145. }
  146. if err = deleteRecoveryCodesByUserID(sess, userID); err != nil {
  147. return fmt.Errorf("deleteRecoveryCodesByUserID: %v", err)
  148. } else if _, err = sess.Insert(recoveryCodes); err != nil {
  149. return fmt.Errorf("insert new recovery codes: %v", err)
  150. }
  151. return sess.Commit()
  152. }
  153. // UseRecoveryCode validates recovery code of given user and marks it is used if valid.
  154. func UseRecoveryCode(userID int64, code string) error {
  155. recoveryCode := new(TwoFactorRecoveryCode)
  156. has, err := x.Where("code = ?", code).And("is_used = ?", false).Get(recoveryCode)
  157. if err != nil {
  158. return fmt.Errorf("get unused code: %v", err)
  159. } else if !has {
  160. return errors.TwoFactorRecoveryCodeNotFound{code}
  161. }
  162. recoveryCode.IsUsed = true
  163. if _, err = x.Id(recoveryCode.ID).Cols("is_used").Update(recoveryCode); err != nil {
  164. return fmt.Errorf("mark code as used: %v", err)
  165. }
  166. return nil
  167. }