207 lines
5.4 KiB
Go
207 lines
5.4 KiB
Go
|
|
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,
|
||
|
|
})
|
||
|
|
}
|