2fa.go 3.7 KB

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