otp.go 2.4 KB

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