2fa.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. package user
  2. import (
  3. "encoding/base64"
  4. "github.com/0xJacky/Nginx-UI/api"
  5. "github.com/0xJacky/Nginx-UI/internal/cache"
  6. "github.com/0xJacky/Nginx-UI/internal/passkey"
  7. "github.com/0xJacky/Nginx-UI/internal/user"
  8. "github.com/0xJacky/Nginx-UI/model"
  9. "github.com/0xJacky/Nginx-UI/query"
  10. "github.com/gin-gonic/gin"
  11. "github.com/go-webauthn/webauthn/webauthn"
  12. "github.com/google/uuid"
  13. "github.com/uozi-tech/cosy"
  14. "net/http"
  15. "strings"
  16. "time"
  17. )
  18. type Status2FA struct {
  19. Enabled bool `json:"enabled"`
  20. OTPStatus bool `json:"otp_status"`
  21. PasskeyStatus bool `json:"passkey_status"`
  22. }
  23. func get2FAStatus(c *gin.Context) (status Status2FA) {
  24. // when accessing the node from the main cluster, there is no user in the context
  25. u, ok := c.Get("user")
  26. if ok {
  27. userPtr := u.(*model.User)
  28. status.OTPStatus = userPtr.EnabledOTP()
  29. status.PasskeyStatus = userPtr.EnabledPasskey() && passkey.Enabled()
  30. status.Enabled = status.OTPStatus || status.PasskeyStatus
  31. }
  32. return
  33. }
  34. func Get2FAStatus(c *gin.Context) {
  35. c.JSON(http.StatusOK, get2FAStatus(c))
  36. }
  37. func SecureSessionStatus(c *gin.Context) {
  38. status2FA := get2FAStatus(c)
  39. if !status2FA.Enabled {
  40. c.JSON(http.StatusOK, gin.H{
  41. "status": false,
  42. })
  43. return
  44. }
  45. ssid := c.GetHeader("X-Secure-Session-ID")
  46. if ssid == "" {
  47. ssid = c.Query("X-Secure-Session-ID")
  48. }
  49. if ssid == "" {
  50. c.JSON(http.StatusOK, gin.H{
  51. "status": false,
  52. })
  53. return
  54. }
  55. u := api.CurrentUser(c)
  56. c.JSON(http.StatusOK, gin.H{
  57. "status": user.VerifySecureSessionID(ssid, u.ID),
  58. })
  59. }
  60. func Start2FASecureSessionByOTP(c *gin.Context) {
  61. var json struct {
  62. OTP string `json:"otp"`
  63. RecoveryCode string `json:"recovery_code"`
  64. }
  65. if !cosy.BindAndValid(c, &json) {
  66. return
  67. }
  68. u := api.CurrentUser(c)
  69. if !u.EnabledOTP() {
  70. api.ErrHandler(c, user.ErrUserNotEnabledOTPAs2FA)
  71. return
  72. }
  73. if json.OTP == "" && json.RecoveryCode == "" {
  74. api.ErrHandler(c, user.ErrOTPOrRecoveryCodeEmpty)
  75. return
  76. }
  77. if err := user.VerifyOTP(u, json.OTP, json.RecoveryCode); err != nil {
  78. api.ErrHandler(c, err)
  79. return
  80. }
  81. sessionId := user.SetSecureSessionID(u.ID)
  82. c.JSON(http.StatusOK, gin.H{
  83. "session_id": sessionId,
  84. })
  85. }
  86. func BeginStart2FASecureSessionByPasskey(c *gin.Context) {
  87. if !passkey.Enabled() {
  88. api.ErrHandler(c, user.ErrWebAuthnNotConfigured)
  89. return
  90. }
  91. webauthnInstance := passkey.GetInstance()
  92. u := api.CurrentUser(c)
  93. options, sessionData, err := webauthnInstance.BeginLogin(u)
  94. if err != nil {
  95. api.ErrHandler(c, err)
  96. return
  97. }
  98. passkeySessionID := uuid.NewString()
  99. cache.Set(passkeySessionID, sessionData, passkeyTimeout)
  100. c.JSON(http.StatusOK, gin.H{
  101. "session_id": passkeySessionID,
  102. "options": options,
  103. })
  104. }
  105. func FinishStart2FASecureSessionByPasskey(c *gin.Context) {
  106. if !passkey.Enabled() {
  107. api.ErrHandler(c, user.ErrWebAuthnNotConfigured)
  108. return
  109. }
  110. passkeySessionID := c.GetHeader("X-Passkey-Session-ID")
  111. sessionDataBytes, ok := cache.Get(passkeySessionID)
  112. if !ok {
  113. api.ErrHandler(c, user.ErrSessionNotFound)
  114. return
  115. }
  116. sessionData := sessionDataBytes.(*webauthn.SessionData)
  117. webauthnInstance := passkey.GetInstance()
  118. u := api.CurrentUser(c)
  119. credential, err := webauthnInstance.FinishLogin(u, *sessionData, c.Request)
  120. if err != nil {
  121. api.ErrHandler(c, err)
  122. return
  123. }
  124. rawID := strings.TrimRight(base64.StdEncoding.EncodeToString(credential.ID), "=")
  125. p := query.Passkey
  126. _, _ = p.Where(p.RawID.Eq(rawID)).Updates(&model.Passkey{
  127. LastUsedAt: time.Now().Unix(),
  128. })
  129. sessionId := user.SetSecureSessionID(u.ID)
  130. c.JSON(http.StatusOK, gin.H{
  131. "session_id": sessionId,
  132. })
  133. }