ilk
This commit is contained in:
169
svc/routes/first_password_change.go
Normal file
169
svc/routes/first_password_change.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"bssapp-backend/auth"
|
||||
"bssapp-backend/internal/auditlog"
|
||||
"bssapp-backend/internal/security"
|
||||
"bssapp-backend/models"
|
||||
"bssapp-backend/repository"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func FirstPasswordChangeHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
// --------------------------------------------------
|
||||
// 1️⃣ JWT CLAIMS
|
||||
// --------------------------------------------------
|
||||
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||||
if !ok || claims == nil {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// 2️⃣ PAYLOAD
|
||||
// --------------------------------------------------
|
||||
var req struct {
|
||||
CurrentPassword string `json:"current_password"`
|
||||
NewPassword string `json:"new_password"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "invalid payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.CurrentPassword == "" || req.NewPassword == "" {
|
||||
http.Error(w, "password fields required", http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// 3️⃣ LOAD USER (mk_dfusr)
|
||||
// --------------------------------------------------
|
||||
var currentHash string
|
||||
err := db.QueryRow(`
|
||||
SELECT password_hash
|
||||
FROM mk_dfusr
|
||||
WHERE id = $1
|
||||
`, claims.ID).Scan(¤tHash)
|
||||
|
||||
if err != nil || currentHash == "" {
|
||||
http.Error(w, "user not found", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// 4️⃣ CURRENT PASSWORD CHECK
|
||||
// --------------------------------------------------
|
||||
if bcrypt.CompareHashAndPassword(
|
||||
[]byte(currentHash),
|
||||
[]byte(req.CurrentPassword),
|
||||
) != nil {
|
||||
http.Error(w, "mevcut şifre hatalı", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// 5️⃣ PASSWORD POLICY
|
||||
// --------------------------------------------------
|
||||
if err := security.ValidatePassword(req.NewPassword); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// 6️⃣ HASH NEW PASSWORD
|
||||
// --------------------------------------------------
|
||||
hash, err := bcrypt.GenerateFromPassword(
|
||||
[]byte(req.NewPassword),
|
||||
bcrypt.DefaultCost,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, "password hash error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// 7️⃣ UPDATE mk_dfusr
|
||||
// --------------------------------------------------
|
||||
_, err = db.Exec(`
|
||||
UPDATE mk_dfusr
|
||||
SET
|
||||
password_hash = $1,
|
||||
force_password_change = false,
|
||||
password_updated_at = NOW(),
|
||||
updated_at = NOW()
|
||||
WHERE id = $2
|
||||
`, string(hash), claims.ID)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "password update failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// 8️⃣ REFRESH TOKEN REVOKE
|
||||
// --------------------------------------------------
|
||||
_ = repository.
|
||||
NewRefreshTokenRepository(db).
|
||||
RevokeAllForUser(claims.ID)
|
||||
|
||||
// --------------------------------------------------
|
||||
// 9️⃣ NEW JWT (TEK DOĞRU YOL)
|
||||
// --------------------------------------------------
|
||||
newClaims := auth.BuildClaimsFromUser(
|
||||
&models.MkUser{
|
||||
ID: claims.ID,
|
||||
Username: claims.Username,
|
||||
RoleCode: claims.RoleCode,
|
||||
V3Username: claims.V3Username,
|
||||
V3UserGroup: claims.V3UserGroup,
|
||||
SessionID: claims.SessionID,
|
||||
ForcePasswordChange: false,
|
||||
},
|
||||
15*time.Minute,
|
||||
)
|
||||
|
||||
newToken, err := auth.GenerateToken(
|
||||
newClaims,
|
||||
claims.Username,
|
||||
false,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, "token generation failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// 🔟 AUDIT
|
||||
// --------------------------------------------------
|
||||
auditlog.ForcePasswordChangeCompleted(
|
||||
r.Context(),
|
||||
claims.ID,
|
||||
"self_change",
|
||||
)
|
||||
|
||||
// --------------------------------------------------
|
||||
// 1️⃣1️⃣ RESPONSE
|
||||
// --------------------------------------------------
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"token": newToken,
|
||||
"user": map[string]any{
|
||||
"id": claims.ID,
|
||||
"username": claims.Username,
|
||||
"force_password_change": false,
|
||||
},
|
||||
})
|
||||
log.Printf("✅ FIRST-PASS claims user=%d role=%s", claims.ID, claims.RoleCode)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user