otp.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. package user
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "strings"
  7. "time"
  8. "github.com/0xJacky/Nginx-UI/api"
  9. "github.com/0xJacky/Nginx-UI/internal/crypto"
  10. "github.com/0xJacky/Nginx-UI/model"
  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 twoFA struct {
  54. Secret string `json:"secret" binding:"required"`
  55. Passcode string `json:"passcode" binding:"required"`
  56. }
  57. if !cosy.BindAndValid(c, &twoFA) {
  58. return
  59. }
  60. if ok := totp.Validate(twoFA.Passcode, twoFA.Secret); !ok {
  61. c.JSON(http.StatusNotAcceptable, gin.H{
  62. "message": "Invalid passcode",
  63. })
  64. return
  65. }
  66. ciphertext, err := crypto.AesEncrypt([]byte(twoFA.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. t := time.Now().Unix()
  78. recoveryCodes := model.RecoveryCodes{Codes: generateRecoveryCodes(16), LastViewed: &t}
  79. codesJson, err := json.Marshal(&recoveryCodes)
  80. if err != nil {
  81. api.ErrHandler(c, err)
  82. return
  83. }
  84. _, err = u.Where(u.ID.Eq(cUser.ID)).Update(u.RecoveryCodes, codesJson)
  85. if err != nil {
  86. api.ErrHandler(c, err)
  87. return
  88. }
  89. c.JSON(http.StatusOK, RecoveryCodesResponse{
  90. Message: "ok",
  91. RecoveryCodes: recoveryCodes,
  92. })
  93. }
  94. func ResetOTP(c *gin.Context) {
  95. cUser := api.CurrentUser(c)
  96. u := query.User
  97. _, err := u.Where(u.ID.Eq(cUser.ID)).UpdateSimple(u.OTPSecret.Null(), u.RecoveryCodes.Null())
  98. if err != nil {
  99. api.ErrHandler(c, err)
  100. return
  101. }
  102. c.JSON(http.StatusOK, gin.H{
  103. "message": "ok",
  104. })
  105. }