otp.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. package user
  2. import (
  3. "bytes"
  4. "crypto/sha1"
  5. "encoding/hex"
  6. "fmt"
  7. "net/http"
  8. "strings"
  9. "github.com/0xJacky/Nginx-UI/api"
  10. "github.com/0xJacky/Nginx-UI/internal/crypto"
  11. "github.com/0xJacky/Nginx-UI/query"
  12. "github.com/0xJacky/Nginx-UI/settings"
  13. "github.com/gin-gonic/gin"
  14. "github.com/pquerna/otp"
  15. "github.com/pquerna/otp/totp"
  16. "github.com/uozi-tech/cosy"
  17. )
  18. func GenerateTOTP(c *gin.Context) {
  19. u := api.CurrentUser(c)
  20. issuer := fmt.Sprintf("Nginx UI %s", settings.NodeSettings.Name)
  21. issuer = strings.TrimSpace(issuer)
  22. otpOpts := totp.GenerateOpts{
  23. Issuer: issuer,
  24. AccountName: u.Name,
  25. Period: 30, // seconds
  26. Digits: otp.DigitsSix,
  27. Algorithm: otp.AlgorithmSHA1,
  28. }
  29. otpKey, err := totp.Generate(otpOpts)
  30. if err != nil {
  31. api.ErrHandler(c, err)
  32. return
  33. }
  34. c.JSON(http.StatusOK, gin.H{
  35. "secret": otpKey.Secret(),
  36. "url": otpKey.URL(),
  37. })
  38. }
  39. func EnrollTOTP(c *gin.Context) {
  40. cUser := api.CurrentUser(c)
  41. if cUser.EnabledOTP() {
  42. c.JSON(http.StatusBadRequest, gin.H{
  43. "message": "User already enrolled",
  44. })
  45. return
  46. }
  47. if settings.NodeSettings.Demo {
  48. c.JSON(http.StatusBadRequest, gin.H{
  49. "message": "This feature is disabled in demo mode",
  50. })
  51. return
  52. }
  53. var json struct {
  54. Secret string `json:"secret" binding:"required"`
  55. Passcode string `json:"passcode" binding:"required"`
  56. }
  57. if !cosy.BindAndValid(c, &json) {
  58. return
  59. }
  60. if ok := totp.Validate(json.Passcode, json.Secret); !ok {
  61. c.JSON(http.StatusNotAcceptable, gin.H{
  62. "message": "Invalid passcode",
  63. })
  64. return
  65. }
  66. ciphertext, err := crypto.AesEncrypt([]byte(json.Secret))
  67. if err != nil {
  68. api.ErrHandler(c, err)
  69. return
  70. }
  71. u := query.User
  72. _, err = u.Where(u.ID.Eq(cUser.ID)).Update(u.OTPSecret, ciphertext)
  73. if err != nil {
  74. api.ErrHandler(c, err)
  75. return
  76. }
  77. recoveryCode := sha1.Sum(ciphertext)
  78. c.JSON(http.StatusOK, gin.H{
  79. "message": "ok",
  80. "recovery_code": hex.EncodeToString(recoveryCode[:]),
  81. })
  82. }
  83. func ResetOTP(c *gin.Context) {
  84. var json struct {
  85. RecoveryCode string `json:"recovery_code"`
  86. }
  87. if !cosy.BindAndValid(c, &json) {
  88. return
  89. }
  90. recoverCode, err := hex.DecodeString(json.RecoveryCode)
  91. if err != nil {
  92. api.ErrHandler(c, err)
  93. return
  94. }
  95. cUser := api.CurrentUser(c)
  96. k := sha1.Sum(cUser.OTPSecret)
  97. if !bytes.Equal(k[:], recoverCode) {
  98. c.JSON(http.StatusBadRequest, gin.H{
  99. "message": "Invalid recovery code",
  100. })
  101. return
  102. }
  103. u := query.User
  104. _, err = u.Where(u.ID.Eq(cUser.ID)).UpdateSimple(u.OTPSecret.Null())
  105. if err != nil {
  106. api.ErrHandler(c, err)
  107. return
  108. }
  109. c.JSON(http.StatusOK, gin.H{
  110. "message": "ok",
  111. })
  112. }