This commit is contained in:
2026-02-11 17:46:22 +03:00
commit eacfacb13b
266 changed files with 51337 additions and 0 deletions

733
svc/routes/login.go Normal file
View File

@@ -0,0 +1,733 @@
package routes
import (
"bssapp-backend/auth"
"bssapp-backend/internal/auditlog"
"bssapp-backend/models"
"bssapp-backend/queries"
"bssapp-backend/repository"
"bssapp-backend/services"
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
"golang.org/x/crypto/bcrypt"
)
/* ======================================================
🔐 LOGIN
====================================================== */
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
func LoginHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
// --------------------------------------------------
// 0⃣ REQUEST
// --------------------------------------------------
var req LoginRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Geçersiz JSON", http.StatusBadRequest)
return
}
login := strings.TrimSpace(req.Username)
pass := req.Password // ⚠️ TRIM YAPMA
if login == "" || pass == "" {
http.Error(w, "Kullanıcı adı veya parola hatalı", http.StatusUnauthorized)
return
}
mkRepo := repository.NewMkUserRepository(db)
// ==================================================
// 1⃣ mk_dfusr ÖNCELİKLİ
// ==================================================
mkUser, err := mkRepo.GetByUsername(login)
if err == nil {
log.Println("🧪 MK USER FROM DB")
log.Printf("🧪 ID=%d role_id=%d role_code='%s' depts=%v",
mkUser.ID,
mkUser.RoleID,
mkUser.RoleCode,
mkUser.DepartmentCodes,
)
}
log.Printf(
"🔎 LOGIN DEBUG | mk_user_found=%v err=%v hash_len=%d",
err == nil,
err,
func() int {
if err == nil {
return len(strings.TrimSpace(mkUser.PasswordHash))
}
return 0
}(),
)
if err == nil {
// mk_dfusr authoritative
if strings.TrimSpace(mkUser.PasswordHash) != "" {
if bcrypt.CompareHashAndPassword(
[]byte(mkUser.PasswordHash),
[]byte(pass),
) != nil {
http.Error(w, "Kullanıcı adı veya parola hatalı", http.StatusUnauthorized)
return
}
_ = mkRepo.TouchLastLogin(mkUser.ID)
writeLoginResponse(w, db, mkUser)
return
}
// password_hash boşsa legacy fallback
} else if err != repository.ErrMkUserNotFound {
log.Println("❌ mk_dfusr lookup error:", err)
http.Error(w, "Giriş yapılamadı", http.StatusInternalServerError)
return
}
// ==================================================
// 2⃣ dfusr FALLBACK (LEGACY)
// ==================================================
log.Println("🟡 LEGACY LOGIN PATH:", login)
legacyRepo := repository.NewUserRepository(db)
legacyUser, err := legacyRepo.GetLegacyUserForLogin(login)
if err != nil || !legacyUser.IsActive {
http.Error(w, "Kullanıcı adı veya parola hatalı", http.StatusUnauthorized)
return
}
log.Printf(
"🔎 LOGIN DEBUG | legacy_upass_len=%d prefix=%s",
len(strings.TrimSpace(legacyUser.Upass)),
func() string {
if len(legacyUser.Upass) >= 4 {
return legacyUser.Upass[:4]
}
return legacyUser.Upass
}(),
)
if !services.CheckPasswordWithLegacy(legacyUser, pass) {
http.Error(w, "Kullanıcı adı veya parola hatalı", http.StatusUnauthorized)
return
}
// ==================================================
// 3⃣ MIGRATION (dfusr → mk_dfusr)
// ==================================================
newHash, err := bcrypt.GenerateFromPassword(
[]byte(pass),
bcrypt.DefaultCost,
)
if err != nil {
http.Error(w, "Şifre üretilemedi", http.StatusInternalServerError)
return
}
mkUser, err = mkRepo.CreateFromLegacy(legacyUser, string(newHash))
if err != nil {
log.Println("❌ CREATE_FROM_LEGACY FAILED:", err)
http.Error(w, "Kullanıcı migrate edilemedi", http.StatusInternalServerError)
return
}
// 🔥 KRİTİK: TOKEN GUARD İÇİN GARANTİ
mkUser.ForcePasswordChange = true
auditlog.Write(auditlog.ActivityLog{
ActionType: "LEGACY_USER_MIGRATED",
ActionCategory: "security",
Description: "dfusr -> mk_dfusr on login",
IsSuccess: true,
})
// ==================================================
// 4⃣ SUCCESS
// ==================================================
writeLoginResponse(w, db, mkUser)
}
}
// ======================================================
// 🔑 LOGIN RESPONSE
// ======================================================
func writeLoginResponse(w http.ResponseWriter, db *sql.DB, user *models.MkUser) {
// 🔥 ROLE GARANTİSİ
if user.RoleID == 0 {
_ = db.QueryRow(`
SELECT dfrole_id
FROM dfrole_usr
WHERE dfusr_id = $1
LIMIT 1
`, user.ID).Scan(&user.RoleID)
}
if user.RoleCode == "" && user.RoleID > 0 {
_ = db.QueryRow(`
SELECT code
FROM dfrole
WHERE id = $1
`, user.RoleID).Scan(&user.RoleCode)
}
log.Println("🧪 LOGIN RESPONSE USER DEBUG")
log.Printf("🧪 user.ID = %d", user.ID)
log.Printf("🧪 user.Username = %s", user.Username)
log.Printf("🧪 user.RoleID = %d", user.RoleID)
log.Printf("🧪 user.RoleCode = '%s'", user.RoleCode)
log.Printf("🧪 user.IsActive = %v", user.IsActive)
permRepo := repository.NewPermissionRepository(db)
perms, _ := permRepo.GetPermissionsByRoleID(user.RoleID)
// ✅ CLAIMS BUILD
claims := auth.BuildClaimsFromUser(user, 15*time.Minute)
token, err := auth.GenerateToken(
claims,
user.Username,
user.ForcePasswordChange,
)
if err != nil {
http.Error(w, "Token üretilemedi", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(map[string]any{
"token": token,
"user": map[string]any{
"id": user.ID,
"username": user.Username,
"email": user.Email,
"is_active": user.IsActive,
"role_id": user.RoleID,
"role_code": user.RoleCode,
"force_password_change": user.ForcePasswordChange,
"v3_username": user.V3Username,
"v3_usergroup": user.V3UserGroup,
},
"permissions": perms,
})
}
/* ======================================================
🔎 LOOKUPS (aynen korunuyor)
====================================================== */
type LookupOption struct {
Value string `json:"value"`
Label string `json:"label"`
}
func GetRoleLookupRoute(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
rows, err := db.Query(queries.GetRoleLookup)
if err != nil {
http.Error(w, "role lookup error", http.StatusInternalServerError)
return
}
defer rows.Close()
var list []LookupOption
for rows.Next() {
var o LookupOption
if err := rows.Scan(&o.Value, &o.Label); err == nil {
list = append(list, o)
}
}
_ = json.NewEncoder(w).Encode(list)
}
}
func GetDepartmentLookupRoute(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
rows, err := db.Query(queries.GetDepartmentLookup)
if err != nil {
http.Error(w, "department lookup error", http.StatusInternalServerError)
return
}
defer rows.Close()
var list []LookupOption
for rows.Next() {
var o LookupOption
if err := rows.Scan(&o.Value, &o.Label); err == nil {
list = append(list, o)
}
}
_ = json.NewEncoder(w).Encode(list)
}
}
func GetPiyasaLookupRoute(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
rows, err := db.Query(queries.GetPiyasaLookup)
if err != nil {
http.Error(w, "piyasa lookup error", http.StatusInternalServerError)
return
}
defer rows.Close()
var list []LookupOption
for rows.Next() {
var o LookupOption
if err := rows.Scan(&o.Value, &o.Label); err == nil {
list = append(list, o)
}
}
_ = json.NewEncoder(w).Encode(list)
}
}
// ======================================================
// 🧾 NEBIM USER LOOKUP
// GET /api/lookups/nebim-users
// ======================================================
func GetNebimUserLookupRoute(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
rows, err := db.Query(queries.GetNebimUserLookup)
if err != nil {
http.Error(w, "nebim lookup error", http.StatusInternalServerError)
return
}
defer rows.Close()
var list []map[string]string
for rows.Next() {
var v, l, g string
if err := rows.Scan(&v, &l, &g); err == nil {
list = append(list, map[string]string{
"value": v,
"label": l,
"user_group_code": g,
})
}
}
_ = json.NewEncoder(w).Encode(list)
}
}
func UserCreateRoute(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
var payload models.UserWrite
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, "Geçersiz payload", http.StatusBadRequest)
return
}
if payload.Code == "" {
http.Error(w, "Kullanıcı kodu zorunludur", http.StatusUnprocessableEntity)
return
}
tx, err := db.Begin()
if err != nil {
http.Error(w, "Transaction başlatılamadı", http.StatusInternalServerError)
return
}
defer tx.Rollback()
var newID int64
err = tx.QueryRow(`
INSERT INTO mk_dfusr (
code,
is_active,
full_name,
email,
mobile,
address,
force_password_change,
last_updated_date
)
VALUES ($1,$2,$3,$4,$5,$6,true,NOW())
RETURNING id
`,
payload.Code,
payload.IsActive,
payload.FullName,
payload.Email,
payload.Mobile,
payload.Address,
).Scan(&newID)
if err != nil {
log.Println("USER INSERT ERROR:", err)
http.Error(w, "Kullanıcı oluşturulamadı", http.StatusInternalServerError)
return
}
// ROLES
for _, role := range payload.Roles {
_, _ = tx.Exec(queries.InsertUserRole, newID, role)
}
// DEPARTMENTS
for _, d := range payload.Departments {
_, _ = tx.Exec(queries.InsertUserDepartment, newID, d.Code)
}
// PIYASALAR
for _, p := range payload.Piyasalar {
_, _ = tx.Exec(queries.InsertUserPiyasa, newID, p.Code)
}
// NEBIM
for _, n := range payload.NebimUsers {
_, _ = tx.Exec(queries.InsertUserNebim, newID, n.Username)
}
if err := tx.Commit(); err != nil {
http.Error(w, "Commit başarısız", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"id": newID,
})
}
}
// ======================================================
// 🔐 ROLES LIST
// GET /api/roles
// ======================================================
func GetRolesHandler(db *sql.DB) http.HandlerFunc {
type RoleRow struct {
ID int64 `json:"id"`
Code string `json:"code"`
Title string `json:"title"`
}
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
rows, err := db.Query(queries.GetRoles)
if err != nil {
http.Error(w, "roles query error", http.StatusInternalServerError)
return
}
defer rows.Close()
var list []RoleRow
for rows.Next() {
var x RoleRow
if err := rows.Scan(&x.ID, &x.Code, &x.Title); err == nil {
list = append(list, x)
}
}
_ = json.NewEncoder(w).Encode(list)
}
}
// ======================================================
// 🏢 DEPARTMENTS LIST
// GET /api/departments
// ======================================================
func GetDepartmentsHandler(db *sql.DB) http.HandlerFunc {
type DeptRow struct {
ID int64 `json:"id"`
Code string `json:"code"`
Title string `json:"title"`
}
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
rows, err := db.Query(queries.GetDepartments)
if err != nil {
http.Error(w, "departments query error", http.StatusInternalServerError)
return
}
defer rows.Close()
var list []DeptRow
for rows.Next() {
var x DeptRow
if err := rows.Scan(&x.ID, &x.Code, &x.Title); err == nil {
list = append(list, x)
}
}
_ = json.NewEncoder(w).Encode(list)
}
}
// ======================================================
// 🌍 PIYASALAR LIST
// GET /api/piyasalar
// ======================================================
func GetPiyasalarHandler(db *sql.DB) http.HandlerFunc {
type PiyRow struct {
Code string `json:"code"`
Title string `json:"title"`
}
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
rows, err := db.Query(queries.GetPiyasalar)
if err != nil {
http.Error(w, "piyasalar query error", http.StatusInternalServerError)
return
}
defer rows.Close()
var list []PiyRow
for rows.Next() {
var x PiyRow
if err := rows.Scan(&x.Code, &x.Title); err == nil {
list = append(list, x)
}
}
_ = json.NewEncoder(w).Encode(list)
}
}
// ======================================================
// 🔗 ROLE → DEPARTMENTS UPDATE
// POST /api/roles/{id}/departments
// body: { "codes": ["D01","D02"] }
// ======================================================
type CodesPayload struct {
Codes []string `json:"codes"`
}
func UpdateRoleDepartmentsHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
roleIDStr := mux.Vars(r)["id"]
roleID, err := strconv.ParseInt(roleIDStr, 10, 64)
if err != nil || roleID <= 0 {
http.Error(w, "Geçersiz role id", http.StatusBadRequest)
return
}
var payload CodesPayload
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, "Geçersiz payload", http.StatusBadRequest)
return
}
tx, err := db.Begin()
if err != nil {
http.Error(w, "Transaction başlatılamadı", http.StatusInternalServerError)
return
}
defer tx.Rollback()
// reset
if _, err := tx.Exec(queries.DeleteRoleDepartments, roleID); err != nil {
http.Error(w, "Role departments silinemedi", http.StatusInternalServerError)
return
}
// insert new
for _, code := range payload.Codes {
code = strings.TrimSpace(code)
if code == "" {
continue
}
if _, err := tx.Exec(queries.InsertRoleDepartment, roleID, code); err != nil {
http.Error(w, "Role department eklenemedi", http.StatusInternalServerError)
return
}
}
if err := tx.Commit(); err != nil {
http.Error(w, "Commit başarısız", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(map[string]any{"success": true})
}
}
// ======================================================
// 🔗 ROLE → PIYASALAR UPDATE
// POST /api/roles/{id}/piyasalar
// body: { "codes": ["TR","EU"] } (piyasa_code list)
// ======================================================
func UpdateRolePiyasalarHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
roleIDStr := mux.Vars(r)["id"]
roleID, err := strconv.ParseInt(roleIDStr, 10, 64)
if err != nil || roleID <= 0 {
http.Error(w, "Geçersiz role id", http.StatusBadRequest)
return
}
var payload CodesPayload
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, "Geçersiz payload", http.StatusBadRequest)
return
}
tx, err := db.Begin()
if err != nil {
http.Error(w, "Transaction başlatılamadı", http.StatusInternalServerError)
return
}
defer tx.Rollback()
// reset
if _, err := tx.Exec(queries.DeleteRolePiyasalar, roleID); err != nil {
http.Error(w, "Role piyasalar silinemedi", http.StatusInternalServerError)
return
}
// insert new
for _, code := range payload.Codes {
code = strings.TrimSpace(code)
if code == "" {
continue
}
if _, err := tx.Exec(queries.InsertRolePiyasa, roleID, code); err != nil {
http.Error(w, "Role piyasa eklenemedi", http.StatusInternalServerError)
return
}
}
if err := tx.Commit(); err != nil {
http.Error(w, "Commit başarısız", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(map[string]any{"success": true})
}
}
// ======================================================
// 👤 USER → ROLES UPDATE
// POST /api/users/{id}/roles
// body: { "codes": ["admin","user"] } (dfrole.code list)
// ======================================================
func UpdateUserRolesHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
// ---------- CLAIMS ----------
claims, ok := auth.GetClaimsFromContext(r.Context())
if !ok || !claims.IsAdmin() {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
// ---------- TARGET USER ----------
userIDStr := mux.Vars(r)["id"]
userID, err := strconv.ParseInt(userIDStr, 10, 64)
if err != nil || userID <= 0 {
http.Error(w, "Geçersiz user id", http.StatusBadRequest)
return
}
// ---------- PAYLOAD ----------
var payload CodesPayload
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, "Geçersiz payload", http.StatusBadRequest)
return
}
// ---------- BEFORE (ROLE LIST) ----------
oldRoles, _ := repository.GetUserRolesByUserID(db, userID)
// ---------- TX ----------
tx, err := db.Begin()
if err != nil {
http.Error(w, "Transaction başlatılamadı", http.StatusInternalServerError)
return
}
defer tx.Rollback()
// reset
if _, err := tx.Exec(queries.DeleteUserRoles, userID); err != nil {
http.Error(w, "User roles silinemedi", http.StatusInternalServerError)
return
}
// insert new
for _, roleCode := range payload.Codes {
roleCode = strings.TrimSpace(roleCode)
if roleCode == "" {
continue
}
if _, err := tx.Exec(queries.InsertUserRole, userID, roleCode); err != nil {
http.Error(w, "User role eklenemedi", http.StatusInternalServerError)
return
}
}
if err := tx.Commit(); err != nil {
http.Error(w, "Commit başarısız", http.StatusInternalServerError)
return
}
// ---------- AFTER (ROLE LIST) ----------
newRoles, _ := repository.GetUserRolesByUserID(db, userID)
// ---------- AUDIT ----------
auditlog.Enqueue(r.Context(), auditlog.ActivityLog{
ActionType: "role_change",
ActionCategory: "user_admin",
ActionTarget: fmt.Sprintf("/api/users/%d/roles", userID),
Description: "user roles updated",
Username: claims.Username,
RoleCode: claims.RoleCode,
DfUsrID: int64(claims.ID),
TargetDfUsrID: userID,
ChangeBefore: map[string]any{
"roles": oldRoles,
},
ChangeAfter: map[string]any{
"roles": newRoles,
},
IsSuccess: true,
})
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
})
}
}