Files
xinfra/authserver/internal/handler/wayen.go
T

207 lines
5.4 KiB
Go
Raw Normal View History

2026-07-13 14:39:56 +08:00
package handler
import (
"errors"
"net/http"
"strings"
"authserver/internal/auth"
"authserver/internal/model"
"authserver/internal/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type selfWayenCredentialPayload struct {
Email string `json:"email"`
Password string `json:"password"`
}
type WayenHandler struct {
db *gorm.DB
wayen *service.WayenService
audit *service.AuditService
}
func NewWayenHandler(db *gorm.DB, wayen *service.WayenService, audit *service.AuditService) *WayenHandler {
return &WayenHandler{db: db, wayen: wayen, audit: audit}
}
func (h *WayenHandler) Login(c *gin.Context) {
claims, ok := CurrentClaims(c)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
return
}
email, _, ok := h.currentUser(c, claims)
if !ok {
return
}
result, err := h.wayen.Login(email, claims.Username)
if err != nil {
h.writeAudit(c, claims.UserID, claims.Username, "deny", err.Error())
switch {
case errors.Is(err, service.ErrWayenEmailMissing):
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
case errors.Is(err, service.ErrWayenNotConfigured):
c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()})
case errors.Is(err, service.ErrWayenCredentialNotFound):
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
case errors.Is(err, service.ErrWayenLoginFailed):
status := http.StatusBadGateway
if strings.Contains(err.Error(), "status 400") {
status = http.StatusBadRequest
}
c.JSON(status, gin.H{"error": err.Error()})
default:
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
return
}
for _, cookie := range result.Cookies {
http.SetCookie(c.Writer, cookie)
}
h.writeAudit(c, claims.UserID, claims.Username, "allow", "")
if strings.Contains(c.GetHeader("Accept"), "application/json") {
c.JSON(http.StatusOK, gin.H{"target_url": result.TargetURL})
return
}
c.Redirect(http.StatusFound, result.TargetURL)
}
func (h *WayenHandler) GetCredential(c *gin.Context) {
claims, ok := CurrentClaims(c)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
return
}
email, _, ok := h.currentUser(c, claims)
if !ok {
return
}
response := gin.H{
"email": email,
"configured": false,
"enabled": false,
}
if email == "" {
c.JSON(http.StatusOK, response)
return
}
var item model.WayenCredential
if err := h.db.Where("email = ?", email).First(&item).Error; err == nil {
response["configured"] = true
response["enabled"] = item.Enabled
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, response)
}
func (h *WayenHandler) SaveCredential(c *gin.Context) {
claims, ok := CurrentClaims(c)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
return
}
var req selfWayenCredentialPayload
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
email, user, ok := h.currentUser(c, claims)
if !ok {
return
}
if trimmed := strings.TrimSpace(req.Email); trimmed != "" {
email = trimmed
}
if email == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "email is required"})
return
}
password := strings.TrimSpace(req.Password)
if password == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "password is required"})
return
}
if err := h.db.Transaction(func(tx *gorm.DB) error {
if user.ID != 0 && strings.TrimSpace(user.Email) != email {
if err := tx.Model(&model.User{}).Where("id = ?", user.ID).Update("email", email).Error; err != nil {
return err
}
}
var item model.WayenCredential
err := tx.Where("email = ?", email).First(&item).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.WayenCredential{
Email: email,
Password: password,
Enabled: true,
}).Error
}
if err != nil {
return err
}
return tx.Model(&item).Updates(map[string]any{
"password": password,
"enabled": true,
}).Error
}); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
h.audit.Write(service.AuditEntry{
ActorUserID: claims.UserID,
ActorUsername: claims.Username,
ClientIP: c.ClientIP(),
UserAgent: c.Request.UserAgent(),
Action: "wayen.credential.save",
ResourceType: "wayen_credential",
ResourceID: email,
Decision: "allow",
})
c.JSON(http.StatusOK, gin.H{"email": email, "configured": true, "enabled": true})
}
func (h *WayenHandler) currentUser(c *gin.Context, claims *auth.Claims) (string, model.User, bool) {
var user model.User
email := strings.TrimSpace(claims.Email)
if claims.UserID != 0 {
if err := h.db.Select("id", "email").Where("id = ?", claims.UserID).First(&user).Error; err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "current user not found"})
return "", user, false
}
if email == "" {
email = strings.TrimSpace(user.Email)
}
}
return email, user, true
}
func (h *WayenHandler) writeAudit(c *gin.Context, userID uint64, username, decision, reason string) {
h.audit.Write(service.AuditEntry{
ActorUserID: userID,
ActorUsername: username,
ClientIP: c.ClientIP(),
UserAgent: c.Request.UserAgent(),
Action: "wayen.login",
ResourceType: "wayen",
Decision: decision,
Reason: reason,
})
}