Merge branch 'feature/Automatic-HTTPS-&-Certificate-Management' into development

This commit is contained in:
Wikid82
2025-11-19 19:57:46 -05:00
4 changed files with 111 additions and 4 deletions

View File

@@ -71,3 +71,29 @@ func (h *AuthHandler) Me(c *gin.Context) {
role, _ := c.Get("role")
c.JSON(http.StatusOK, gin.H{"user_id": userID, "role": role})
}
type ChangePasswordRequest struct {
OldPassword string `json:"old_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required,min=8"`
}
func (h *AuthHandler) ChangePassword(c *gin.Context) {
var req ChangePasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
userID, exists := c.Get("userID")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
if err := h.authService.ChangePassword(userID.(uint), req.OldPassword, req.NewPassword); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Password updated successfully"})
}

View File

@@ -47,6 +47,7 @@ func Register(router *gin.Engine, db *gorm.DB, cfg config.Config) error {
{
protected.POST("/auth/logout", authHandler.Logout)
protected.GET("/auth/me", authHandler.Me)
protected.POST("/auth/change-password", authHandler.ChangePassword)
}
proxyHostHandler := handlers.NewProxyHostHandler(db)

View File

@@ -104,6 +104,23 @@ func (s *AuthService) GenerateToken(user *models.User) (string, error) {
return token.SignedString([]byte(s.config.JWTSecret))
}
func (s *AuthService) ChangePassword(userID uint, oldPassword, newPassword string) error {
var user models.User
if err := s.db.First(&user, userID).Error; err != nil {
return errors.New("user not found")
}
if !user.CheckPassword(oldPassword) {
return errors.New("invalid current password")
}
if err := user.SetPassword(newPassword); err != nil {
return err
}
return s.db.Save(&user).Error
}
func (s *AuthService) ValidateToken(tokenString string) (*Claims, error) {
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {

View File

@@ -1,11 +1,74 @@
import { useState } from 'react'
import { Card } from '../components/ui/Card'
import { Input } from '../components/ui/Input'
import { Button } from '../components/ui/Button'
import { toast } from '../components/Toast'
import client from '../api/client'
export default function Settings() {
const [oldPassword, setOldPassword] = useState('')
const [newPassword, setNewPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [loading, setLoading] = useState(false)
const handleChangePassword = async (e: React.FormEvent) => {
e.preventDefault()
if (newPassword !== confirmPassword) {
toast.error('New passwords do not match')
return
}
setLoading(true)
try {
await client.post('/auth/change-password', {
old_password: oldPassword,
new_password: newPassword,
})
toast.success('Password updated successfully')
setOldPassword('')
setNewPassword('')
setConfirmPassword('')
} catch (err: any) {
toast.error(err.response?.data?.error || 'Failed to update password')
} finally {
setLoading(false)
}
}
return (
<div className="p-8">
<h1 className="text-3xl font-bold text-white mb-6">Settings</h1>
<div className="bg-dark-card rounded-lg border border-gray-800 p-6">
<div className="text-gray-400">
Settings page coming soon...
</div>
<div className="grid gap-6">
<Card title="Change Password" className="max-w-md">
<form onSubmit={handleChangePassword} className="space-y-4">
<Input
label="Current Password"
type="password"
value={oldPassword}
onChange={e => setOldPassword(e.target.value)}
required
/>
<Input
label="New Password"
type="password"
value={newPassword}
onChange={e => setNewPassword(e.target.value)}
required
minLength={8}
/>
<Input
label="Confirm New Password"
type="password"
value={confirmPassword}
onChange={e => setConfirmPassword(e.target.value)}
required
minLength={8}
/>
<Button type="submit" isLoading={loading}>
Update Password
</Button>
</form>
</Card>
</div>
</div>
)