otp.go 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. package user
  2. import (
  3. "bytes"
  4. "crypto/sha1"
  5. "encoding/hex"
  6. "fmt"
  7. "time"
  8. "github.com/0xJacky/Nginx-UI/internal/cache"
  9. "github.com/0xJacky/Nginx-UI/internal/crypto"
  10. "github.com/0xJacky/Nginx-UI/internal/notification"
  11. "github.com/0xJacky/Nginx-UI/model"
  12. "github.com/0xJacky/Nginx-UI/query"
  13. "github.com/google/uuid"
  14. "github.com/pquerna/otp/totp"
  15. )
  16. func VerifyOTP(user *model.User, otp, recoveryCode string) (err error) {
  17. if otp != "" {
  18. decrypted, err := crypto.AesDecrypt(user.OTPSecret)
  19. if err != nil {
  20. return err
  21. }
  22. if ok := totp.Validate(otp, string(decrypted)); !ok {
  23. return ErrOTPCode
  24. }
  25. } else {
  26. // get user from db
  27. u := query.User
  28. user, err = u.Where(u.ID.Eq(user.ID)).First()
  29. if err != nil {
  30. return err
  31. }
  32. // legacy recovery code
  33. if !user.RecoveryCodeGenerated() {
  34. if user.OTPSecret == nil {
  35. return ErrTOTPNotEnabled
  36. }
  37. recoverCode, err := hex.DecodeString(recoveryCode)
  38. if err != nil {
  39. return err
  40. }
  41. k := sha1.Sum(user.OTPSecret)
  42. if !bytes.Equal(k[:], recoverCode) {
  43. return ErrRecoveryCode
  44. }
  45. }
  46. // check recovery code
  47. usedCount := 0
  48. verified := false
  49. for _, code := range user.RecoveryCodes.Codes {
  50. if code.Code == recoveryCode && code.UsedTime == nil {
  51. t := time.Now().Unix()
  52. code.UsedTime = &t
  53. _, err = u.Where(u.ID.Eq(user.ID)).Updates(user)
  54. if err != nil {
  55. return err
  56. }
  57. verified = true
  58. }
  59. if code.UsedTime != nil {
  60. usedCount++
  61. }
  62. }
  63. if verified && usedCount == len(user.RecoveryCodes.Codes) {
  64. notification.Warning("All Recovery Codes Have Been Used", "Please generate new recovery codes in the preferences immediately to prevent lockout.", nil)
  65. }
  66. return ErrRecoveryCode
  67. }
  68. return
  69. }
  70. func secureSessionIDCacheKey(sessionId string) string {
  71. return fmt.Sprintf("2fa_secure_session:_%s", sessionId)
  72. }
  73. func SetSecureSessionID(userId uint64) (sessionId string) {
  74. sessionId = uuid.NewString()
  75. cache.Set(secureSessionIDCacheKey(sessionId), userId, 5*time.Minute)
  76. return
  77. }
  78. func VerifySecureSessionID(sessionId string, userId uint64) bool {
  79. if v, ok := cache.Get(secureSessionIDCacheKey(sessionId)); ok {
  80. if v.(uint64) == userId {
  81. return true
  82. }
  83. }
  84. return false
  85. }