Add Certificates page and Traefik API frontend client

This commit is contained in:
backup 2026-09-02 12:42:52 -05:00
commit 7fc90524b5
40 changed files with 6585 additions and 359 deletions

View file

@ -37,6 +37,13 @@ func NewAuthHandler(
}
func (h *AuthHandler) Login(c *gin.Context) {
// Rate limiting per IP
clientIP := c.ClientIP()
if !auth.DefaultLoginLimiter.Allow(clientIP) {
c.JSON(http.StatusTooManyRequests, gin.H{"error": "too many login attempts, try again later"})
return
}
var req models.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
@ -45,14 +52,17 @@ func (h *AuthHandler) Login(c *gin.Context) {
user, err := h.userRepo.GetByUsername(req.Username)
if err != nil || user == nil {
auth.DefaultLoginLimiter.RecordFailure(clientIP)
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid username or password"})
return
}
if !auth.CheckPassword(req.Password, user.PasswordHash) {
auth.DefaultLoginLimiter.RecordFailure(clientIP)
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid username or password"})
return
}
auth.DefaultLoginLimiter.RecordSuccess(clientIP)
sessionData, err := auth.NewSessionData(user.ID)
if err != nil {
@ -60,17 +70,20 @@ func (h *AuthHandler) Login(c *gin.Context) {
return
}
sessionID, err := auth.GenerateSessionID()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create session"})
return
}
session := &models.Session{
ID: sessionData.CreatedAt.Format("20060102150405") + "-" + sessionData.CSRFToken[:8],
ID: sessionID,
UserID: user.ID,
CSRFToken: sessionData.CSRFToken,
CreatedAt: sessionData.CreatedAt,
ExpiresAt: sessionData.ExpiresAt,
}
// Use a proper UUID for session ID
session.ID, _ = auth.GenerateSessionID()
if err := h.sessionRepo.Create(session); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save session"})
return
@ -109,13 +122,32 @@ func (h *AuthHandler) Me(c *gin.Context) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authenticated"})
return
}
c.JSON(http.StatusOK, gin.H{
// Include current CSRF token for bootstrap after page reload
var csrfToken string
if sVal, exists := c.Get("session"); exists {
if s, ok := sVal.(*models.Session); ok && s != nil {
csrfToken = s.CSRFToken
}
}
resp := gin.H{
"id": user.ID,
"username": user.Username,
"email": user.Email,
"role": user.Role,
})
}
if csrfToken != "" {
resp["csrf_token"] = csrfToken
}
c.JSON(http.StatusOK, resp)
}
func (h *AuthHandler) GetCSRF(c *gin.Context) {
val, _ := c.Get("session"); sess, _ := val.(*models.Session)
if sess == nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authenticated"})
return
}
c.JSON(http.StatusOK, gin.H{"csrf_token": sess.CSRFToken})
}
func (h *AuthHandler) setSessionCookie(c *gin.Context, sessionID string, expiresAt time.Time) {