feat: 2FA authorization for web terminal

This commit is contained in:
Jacky 2024-07-23 20:35:32 +08:00
parent 802d05f692
commit 3a22861640
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
15 changed files with 359 additions and 54 deletions

View file

@ -86,7 +86,7 @@ func Login(c *gin.Context) {
}
// Check if the user enables 2FA
if len(u.OTPSecret) > 0 {
if u.EnabledOTP() {
if json.OTP == "" && json.RecoveryCode == "" {
c.JSON(http.StatusOK, LoginResponse{
Message: "The user has enabled 2FA",

View file

@ -8,6 +8,7 @@ import (
"fmt"
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/crypto"
"github.com/0xJacky/Nginx-UI/internal/user"
"github.com/0xJacky/Nginx-UI/query"
"github.com/0xJacky/Nginx-UI/settings"
"github.com/gin-gonic/gin"
@ -67,8 +68,8 @@ func GenerateTOTP(c *gin.Context) {
}
func EnrollTOTP(c *gin.Context) {
user := api.CurrentUser(c)
if len(user.OTPSecret) > 0 {
cUser := api.CurrentUser(c)
if cUser.EnabledOTP() {
c.JSON(http.StatusBadRequest, gin.H{
"message": "User already enrolled",
})
@ -109,7 +110,7 @@ func EnrollTOTP(c *gin.Context) {
}
u := query.Auth
_, err = u.Where(u.ID.Eq(user.ID)).Update(u.OTPSecret, ciphertext)
_, err = u.Where(u.ID.Eq(cUser.ID)).Update(u.OTPSecret, ciphertext)
if err != nil {
api.ErrHandler(c, err)
return
@ -135,8 +136,8 @@ func ResetOTP(c *gin.Context) {
api.ErrHandler(c, err)
return
}
user := api.CurrentUser(c)
k := sha1.Sum(user.OTPSecret)
cUser := api.CurrentUser(c)
k := sha1.Sum(cUser.OTPSecret)
if !bytes.Equal(k[:], recoverCode) {
c.JSON(http.StatusBadRequest, gin.H{
"message": "Invalid recovery code",
@ -145,7 +146,7 @@ func ResetOTP(c *gin.Context) {
}
u := query.Auth
_, err = u.Where(u.ID.Eq(user.ID)).UpdateSimple(u.OTPSecret.Null())
_, err = u.Where(u.ID.Eq(cUser.ID)).UpdateSimple(u.OTPSecret.Null())
if err != nil {
api.ErrHandler(c, err)
return
@ -161,3 +162,40 @@ func OTPStatus(c *gin.Context) {
"status": len(api.CurrentUser(c).OTPSecret) > 0,
})
}
func StartSecure2FASession(c *gin.Context) {
var json struct {
OTP string `json:"otp"`
RecoveryCode string `json:"recovery_code"`
}
if !api.BindAndValid(c, &json) {
return
}
u := api.CurrentUser(c)
if !u.EnabledOTP() {
c.JSON(http.StatusBadRequest, gin.H{
"message": "User not configured with 2FA",
})
return
}
if json.OTP == "" && json.RecoveryCode == "" {
c.JSON(http.StatusBadRequest, LoginResponse{
Message: "The user has enabled 2FA",
})
return
}
if err := user.VerifyOTP(u, json.OTP, json.RecoveryCode); err != nil {
c.JSON(http.StatusBadRequest, LoginResponse{
Message: "Invalid 2FA or recovery code",
})
return
}
sessionId := user.SetSecureSessionID(u.ID)
c.JSON(http.StatusOK, gin.H{
"session_id": sessionId,
})
}

View file

@ -22,5 +22,6 @@ func InitUserRouter(r *gin.RouterGroup) {
r.GET("/otp_status", OTPStatus)
r.GET("/otp_secret", GenerateTOTP)
r.POST("/otp_enroll", EnrollTOTP)
r.POST("/otp_reset", ResetOTP)
r.POST("/otp_reset", ResetOTP)
r.POST("/otp_secure_session", StartSecure2FASession)
}