2fa.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  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. "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 !api.BindAndValid(c, &json) {
  66. return
  67. }
  68. u := api.CurrentUser(c)
  69. if !u.EnabledOTP() {
  70. c.JSON(http.StatusBadRequest, gin.H{
  71. "message": "User has not configured OTP as 2FA",
  72. })
  73. return
  74. }
  75. if json.OTP == "" && json.RecoveryCode == "" {
  76. c.JSON(http.StatusBadRequest, LoginResponse{
  77. Message: "The user has enabled OTP as 2FA",
  78. })
  79. return
  80. }
  81. if err := user.VerifyOTP(u, json.OTP, json.RecoveryCode); err != nil {
  82. c.JSON(http.StatusBadRequest, LoginResponse{
  83. Message: "Invalid OTP or recovery code",
  84. })
  85. return
  86. }
  87. sessionId := user.SetSecureSessionID(u.ID)
  88. c.JSON(http.StatusOK, gin.H{
  89. "session_id": sessionId,
  90. })
  91. }
  92. func BeginStart2FASecureSessionByPasskey(c *gin.Context) {
  93. if !passkey.Enabled() {
  94. api.ErrHandler(c, fmt.Errorf("WebAuthn settings are not configured"))
  95. return
  96. }
  97. webauthnInstance := passkey.GetInstance()
  98. u := api.CurrentUser(c)
  99. options, sessionData, err := webauthnInstance.BeginLogin(u)
  100. if err != nil {
  101. api.ErrHandler(c, err)
  102. return
  103. }
  104. passkeySessionID := uuid.NewString()
  105. cache.Set(passkeySessionID, sessionData, passkeyTimeout)
  106. c.JSON(http.StatusOK, gin.H{
  107. "session_id": passkeySessionID,
  108. "options": options,
  109. })
  110. }
  111. func FinishStart2FASecureSessionByPasskey(c *gin.Context) {
  112. if !passkey.Enabled() {
  113. api.ErrHandler(c, fmt.Errorf("WebAuthn settings are not configured"))
  114. return
  115. }
  116. passkeySessionID := c.GetHeader("X-Passkey-Session-ID")
  117. sessionDataBytes, ok := cache.Get(passkeySessionID)
  118. if !ok {
  119. api.ErrHandler(c, fmt.Errorf("session not found"))
  120. return
  121. }
  122. sessionData := sessionDataBytes.(*webauthn.SessionData)
  123. webauthnInstance := passkey.GetInstance()
  124. u := api.CurrentUser(c)
  125. credential, err := webauthnInstance.FinishLogin(u, *sessionData, c.Request)
  126. if err != nil {
  127. api.ErrHandler(c, err)
  128. return
  129. }
  130. rawID := strings.TrimRight(base64.StdEncoding.EncodeToString(credential.ID), "=")
  131. p := query.Passkey
  132. _, _ = p.Where(p.RawID.Eq(rawID)).Updates(&model.Passkey{
  133. LastUsedAt: time.Now().Unix(),
  134. })
  135. sessionId := user.SetSecureSessionID(u.ID)
  136. c.JSON(http.StatusOK, gin.H{
  137. "session_id": sessionId,
  138. })
  139. }