Merge remote-tracking branch 'origin/master'
This commit is contained in:
33
svc/.env.local
Normal file
33
svc/.env.local
Normal file
@@ -0,0 +1,33 @@
|
||||
# ===============================
|
||||
# SECURITY
|
||||
# ===============================
|
||||
JWT_SECRET=bssapp_super_secret_key_1234567890
|
||||
PASSWORD_RESET_SECRET=1dc7d6d52fd0459a8b1f288a6590428e760f54339f8e47beb20db36b6df6070b
|
||||
|
||||
# ===============================
|
||||
# URLS (PRODUCTION)
|
||||
# ===============================
|
||||
APP_FRONTEND_URL=http://46.224.33.150
|
||||
API_URL=http://46.224.33.150
|
||||
|
||||
# Eğer Nginx ile /api varsa:
|
||||
# API_URL=http://46.224.33.150/api
|
||||
|
||||
# ===============================
|
||||
# UI
|
||||
# ===============================
|
||||
UI_DIR=/opt/bssapp/ui/dist
|
||||
|
||||
# ===============================
|
||||
# DATABASES
|
||||
# ===============================
|
||||
POSTGRES_CONN=host=46.224.33.150 port=5432 user=postgres password=tayitkan dbname=baggib2b sslmode=disable
|
||||
MSSQL_CONN=sqlserver://sa:Gil_0150@100.127.186.137:1433?database=BAGGI_V3&encrypt=disable
|
||||
|
||||
# ===============================
|
||||
# PDF
|
||||
# ===============================
|
||||
PDF_FONT_DIR=/opt/bssapp/svc/fonts
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=8080
|
||||
|
||||
@@ -12,12 +12,11 @@ import (
|
||||
|
||||
var MssqlDB *sql.DB
|
||||
|
||||
// ConnectMSSQL MSSQL baglantisini ortam degiskeninden baslatir.
|
||||
func ConnectMSSQL() {
|
||||
connString := strings.TrimSpace(os.Getenv("MSSQL_CONN"))
|
||||
|
||||
if connString == "" {
|
||||
// Fallback
|
||||
connString = "server=100.127.186.137;user id=sa;password=Gi l_0150;port=1433;database=BAGGI_V3"
|
||||
log.Fatal("MSSQL_CONN tanımlı değil")
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -26,12 +25,11 @@ func ConnectMSSQL() {
|
||||
log.Fatal("MSSQL bağlantı hatası:", err)
|
||||
}
|
||||
|
||||
err = MssqlDB.Ping()
|
||||
if err != nil {
|
||||
if err = MssqlDB.Ping(); err != nil {
|
||||
log.Fatal("MSSQL erişilemiyor:", err)
|
||||
}
|
||||
|
||||
fmt.Println("✅ MSSQL bağlantısı başarılı!")
|
||||
fmt.Println("MSSQL bağlantısı başarılı")
|
||||
}
|
||||
|
||||
func GetDB() *sql.DB {
|
||||
|
||||
@@ -13,14 +13,11 @@ import (
|
||||
|
||||
var PgDB *sql.DB
|
||||
|
||||
// ConnectPostgres → PostgreSQL veritabanına bağlanır
|
||||
// ConnectPostgres PostgreSQL veritabanına bağlanır.
|
||||
func ConnectPostgres() (*sql.DB, error) {
|
||||
// Bağlantı stringi (istersen .env’den oku)
|
||||
connStr := os.Getenv("POSTGRES_CONN")
|
||||
connStr := strings.TrimSpace(os.Getenv("POSTGRES_CONN"))
|
||||
if connStr == "" {
|
||||
// fallback → sabit tanımlı bağlantı
|
||||
connStr = "host= 46.224.33.150 port=5432 user=postgres password=tayitkan dbname=baggib2b sslmode=disable"
|
||||
//connStr = "host=172.16.0.3 port=5432 user=postgres password=tayitkan dbname=baggib2b sslmode=disable"
|
||||
return nil, fmt.Errorf("POSTGRES_CONN tanımlı değil")
|
||||
}
|
||||
|
||||
db, err := sql.Open("postgres", connStr)
|
||||
@@ -28,15 +25,13 @@ func ConnectPostgres() (*sql.DB, error) {
|
||||
return nil, fmt.Errorf("PostgreSQL bağlantı hatası: %w", err)
|
||||
}
|
||||
|
||||
// =======================================================
|
||||
// 🔹 BAĞLANTI HAVUZU (AUDIT LOG UYUMLU)
|
||||
// =======================================================
|
||||
db.SetMaxOpenConns(30) // audit + api paralel çalışsın
|
||||
// Bağlantı havuzu ayarları (audit log uyumlu).
|
||||
db.SetMaxOpenConns(30)
|
||||
db.SetMaxIdleConns(10)
|
||||
db.SetConnMaxLifetime(30 * time.Minute)
|
||||
db.SetConnMaxIdleTime(5 * time.Minute) // 🔥 uzun idle audit bağlantılarını kapat
|
||||
db.SetConnMaxIdleTime(5 * time.Minute)
|
||||
|
||||
// 🔹 Test et
|
||||
// Bağlantıyı test et.
|
||||
if err = db.Ping(); err != nil {
|
||||
// Some managed PostgreSQL servers require TLS. If the current DSN uses
|
||||
// sslmode=disable and server rejects with "no encryption", retry once
|
||||
@@ -45,7 +40,7 @@ func ConnectPostgres() (*sql.DB, error) {
|
||||
strings.Contains(err.Error(), "no encryption") &&
|
||||
strings.Contains(strings.ToLower(connStr), "sslmode=disable") {
|
||||
secureConnStr := strings.Replace(connStr, "sslmode=disable", "sslmode=require", 1)
|
||||
log.Println("⚠️ PostgreSQL requires TLS, retrying with sslmode=require")
|
||||
log.Println("PostgreSQL TLS gerektiriyor, sslmode=require ile tekrar deneniyor")
|
||||
|
||||
_ = db.Close()
|
||||
db, err = sql.Open("postgres", secureConnStr)
|
||||
@@ -66,13 +61,12 @@ func ConnectPostgres() (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("✅ PostgreSQL bağlantısı başarılı!")
|
||||
log.Println("PostgreSQL bağlantısı başarılı")
|
||||
PgDB = db
|
||||
return db, nil
|
||||
|
||||
}
|
||||
|
||||
// GetPostgresUsers → test amaçlı ilk 5 kullanıcıyı listeler
|
||||
// GetPostgresUsers test amaçlı ilk 5 kullanıcıyı listeler.
|
||||
func GetPostgresUsers(db *sql.DB) error {
|
||||
query := `SELECT id, code, email FROM mk_dfusr ORDER BY id LIMIT 5`
|
||||
rows, err := db.Query(query)
|
||||
@@ -81,14 +75,14 @@ func GetPostgresUsers(db *sql.DB) error {
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
fmt.Println("📋 İlk 5 PostgreSQL kullanıcısı:")
|
||||
fmt.Println("İlk 5 PostgreSQL kullanıcısı:")
|
||||
for rows.Next() {
|
||||
var id int
|
||||
var code, email string
|
||||
if err := rows.Scan(&id, &code, &email); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf(" ➜ ID: %-4d | USER: %-20s | EMAIL: %s\n", id, code, email)
|
||||
fmt.Printf(" -> ID: %-4d | USER: %-20s | EMAIL: %s\n", id, code, email)
|
||||
}
|
||||
|
||||
return rows.Err()
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
// Ana tabloyu getiren fonksiyon (Vue header tablosu için)
|
||||
func GetStatements(params models.StatementParams) ([]models.StatementHeader, error) {
|
||||
|
||||
// AccountCode normalize: "ZLA0127" → "ZLA 0127"
|
||||
if len(params.AccountCode) == 7 && strings.ContainsAny(params.AccountCode, "0123456789") {
|
||||
params.AccountCode = params.AccountCode[:3] + " " + params.AccountCode[3:]
|
||||
@@ -27,7 +28,6 @@ func GetStatements(params models.StatementParams) ([]models.StatementHeader, err
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
// Escape tek tırnak to avoid malformed SQL when list is injected into IN (...).
|
||||
quoted = append(quoted, fmt.Sprintf("'%s'", strings.ReplaceAll(v, "'", "''")))
|
||||
}
|
||||
if len(quoted) > 0 {
|
||||
@@ -36,7 +36,6 @@ func GetStatements(params models.StatementParams) ([]models.StatementHeader, err
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(`
|
||||
|
||||
;WITH CurrDesc AS (
|
||||
SELECT
|
||||
CurrAccCode,
|
||||
@@ -45,89 +44,102 @@ func GetStatements(params models.StatementParams) ([]models.StatementHeader, err
|
||||
WHERE LangCode = @LangCode
|
||||
GROUP BY CurrAccCode
|
||||
),
|
||||
|
||||
/* =========================================================
|
||||
✅ Bu aralıkta hareket var mı?
|
||||
Varsa : Devir = startdate öncesi
|
||||
Yoksa : Devir = enddate dahil (enddate itibariyle bakiye)
|
||||
========================================================= */
|
||||
HasMovement AS (
|
||||
SELECT
|
||||
CASE WHEN EXISTS (
|
||||
SELECT 1
|
||||
FROM trCurrAccBook b
|
||||
INNER JOIN CurrAccBookATAttributesFilter f
|
||||
ON f.CurrAccBookID = b.CurrAccBookID
|
||||
AND f.ATAtt01 IN (%s)
|
||||
WHERE b.CurrAccCode LIKE '%%' + @Carikod + '%%'
|
||||
AND b.DocumentDate BETWEEN @startdate AND @enddate
|
||||
) THEN 1 ELSE 0 END AS HasMov
|
||||
),
|
||||
|
||||
/* =========================================================
|
||||
✅ Opening (Devir) — TEK CARİ KOD ALTINDA KONSOLİDE
|
||||
Cari_Kod = @Carikod (sabit)
|
||||
========================================================= */
|
||||
Opening AS (
|
||||
SELECT
|
||||
b.CurrAccCode AS Cari_Kod,
|
||||
b.DocCurrencyCode AS Para_Birimi,
|
||||
SUM(c.Debit - c.Credit) AS Devir_Bakiyesi
|
||||
|
||||
SELECT
|
||||
@Carikod AS Cari_Kod,
|
||||
b.DocCurrencyCode AS Para_Birimi,
|
||||
SUM(ISNULL(c.Debit,0) - ISNULL(c.Credit,0)) AS Devir_Bakiyesi
|
||||
FROM trCurrAccBook b
|
||||
|
||||
CROSS JOIN HasMovement hm
|
||||
INNER JOIN CurrAccBookATAttributesFilter f2
|
||||
ON f2.CurrAccBookID = b.CurrAccBookID
|
||||
AND f2.ATAtt01 IN (%s)
|
||||
LEFT JOIN trCurrAccBookCurrency c
|
||||
ON c.CurrAccBookID = b.CurrAccBookID
|
||||
AND c.CurrencyCode = b.DocCurrencyCode
|
||||
|
||||
WHERE b.CurrAccCode LIKE '%%' + @Carikod + '%%'
|
||||
AND b.DocumentDate < @startdate
|
||||
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM CurrAccBookATAttributesFilter f2
|
||||
WHERE f2.CurrAccBookID = b.CurrAccBookID
|
||||
AND f2.ATAtt01 IN (%s)
|
||||
AND (
|
||||
(hm.HasMov = 1 AND b.DocumentDate < @startdate) -- hareket varsa: klasik devir
|
||||
OR (hm.HasMov = 0 AND b.DocumentDate <= @enddate) -- hareket yoksa: enddate itibariyle bakiye
|
||||
)
|
||||
|
||||
GROUP BY
|
||||
b.CurrAccCode,
|
||||
b.DocCurrencyCode
|
||||
),
|
||||
OpeningByCurrency AS (
|
||||
SELECT
|
||||
Para_Birimi,
|
||||
SUM(Devir_Bakiyesi) AS Devir_Bakiyesi
|
||||
FROM Opening
|
||||
GROUP BY Para_Birimi
|
||||
GROUP BY b.DocCurrencyCode
|
||||
),
|
||||
|
||||
/* =========================================================
|
||||
✅ Hareketler (Movements) — TEK CARİ KOD ALTINDA KONSOLİDE
|
||||
Cari_Kod = @Carikod (sabit)
|
||||
Running sadece aralıktaki hareketlerden gelir.
|
||||
========================================================= */
|
||||
Movements AS (
|
||||
SELECT
|
||||
b.CurrAccCode AS Cari_Kod,
|
||||
d.CurrAccDescription AS Cari_Isim,
|
||||
SELECT
|
||||
@Carikod AS Cari_Kod,
|
||||
|
||||
CONVERT(varchar(10), b.DocumentDate, 23) AS Belge_Tarihi,
|
||||
CONVERT(varchar(10), b.DueDate, 23) AS Vade_Tarihi,
|
||||
COALESCE(
|
||||
(SELECT TOP 1 cd.CurrAccDescription
|
||||
FROM CurrDesc cd
|
||||
WHERE cd.CurrAccCode = @Carikod),
|
||||
(SELECT TOP 1 cd.CurrAccDescription
|
||||
FROM CurrDesc cd
|
||||
WHERE cd.CurrAccCode LIKE '%%' + @Carikod + '%%'
|
||||
ORDER BY cd.CurrAccCode)
|
||||
) AS Cari_Isim,
|
||||
|
||||
b.RefNumber AS Belge_No,
|
||||
CONVERT(varchar(10), b.DocumentDate, 23) AS Belge_Tarihi,
|
||||
CONVERT(varchar(10), b.DueDate, 23) AS Vade_Tarihi,
|
||||
|
||||
b.RefNumber AS Belge_No,
|
||||
b.BaseApplicationCode AS Islem_Tipi,
|
||||
b.LineDescription AS Aciklama,
|
||||
b.LineDescription AS Aciklama,
|
||||
|
||||
b.DocCurrencyCode AS Para_Birimi,
|
||||
b.DocCurrencyCode AS Para_Birimi,
|
||||
|
||||
c.Debit AS Borc,
|
||||
c.Credit AS Alacak,
|
||||
ISNULL(c.Debit,0) AS Borc,
|
||||
ISNULL(c.Credit,0) AS Alacak,
|
||||
|
||||
SUM(c.Debit - c.Credit)
|
||||
SUM(ISNULL(c.Debit,0) - ISNULL(c.Credit,0))
|
||||
OVER (
|
||||
PARTITION BY b.CurrAccCode, c.CurrencyCode
|
||||
PARTITION BY b.DocCurrencyCode
|
||||
ORDER BY b.DocumentDate, b.CurrAccBookID
|
||||
) AS Hareket_Bakiyesi,
|
||||
|
||||
f.ATAtt01 AS Parislemtipi
|
||||
|
||||
FROM trCurrAccBook b
|
||||
|
||||
LEFT JOIN CurrDesc d
|
||||
ON b.CurrAccCode = d.CurrAccCode
|
||||
|
||||
INNER JOIN CurrAccBookATAttributesFilter f
|
||||
ON f.CurrAccBookID = b.CurrAccBookID
|
||||
AND f.ATAtt01 IN (%s)
|
||||
LEFT JOIN trCurrAccBookCurrency c
|
||||
ON b.CurrAccBookID = c.CurrAccBookID
|
||||
AND b.DocCurrencyCode = c.CurrencyCode
|
||||
|
||||
LEFT JOIN CurrAccBookATAttributesFilter f
|
||||
ON b.CurrAccBookID = f.CurrAccBookID
|
||||
ON c.CurrAccBookID = b.CurrAccBookID
|
||||
AND c.CurrencyCode = b.DocCurrencyCode
|
||||
|
||||
WHERE b.CurrAccCode LIKE '%%' + @Carikod + '%%'
|
||||
AND b.DocumentDate BETWEEN @startdate AND @enddate
|
||||
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM CurrAccBookATAttributesFilter f2
|
||||
WHERE f2.CurrAccBookID = b.CurrAccBookID
|
||||
AND f2.ATAtt01 IN (%s)
|
||||
)
|
||||
)
|
||||
|
||||
SELECT
|
||||
SELECT
|
||||
m.Cari_Kod,
|
||||
m.Cari_Isim,
|
||||
m.Belge_Tarihi,
|
||||
@@ -136,27 +148,28 @@ SELECT
|
||||
m.Islem_Tipi,
|
||||
m.Aciklama,
|
||||
m.Para_Birimi,
|
||||
|
||||
m.Borc,
|
||||
m.Alacak,
|
||||
|
||||
/* ✅ Bakiye = Devir + Aralıktaki Running */
|
||||
ISNULL(o.Devir_Bakiyesi,0) + m.Hareket_Bakiyesi AS Bakiye,
|
||||
|
||||
m.Parislemtipi AS Parislemler
|
||||
|
||||
FROM Movements m
|
||||
|
||||
LEFT JOIN Opening o
|
||||
ON o.Cari_Kod = m.Cari_Kod
|
||||
ON o.Cari_Kod = m.Cari_Kod
|
||||
AND o.Para_Birimi = m.Para_Birimi
|
||||
|
||||
|
||||
UNION ALL
|
||||
|
||||
|
||||
/* ✅ Devir satırı sadece Opening’den */
|
||||
SELECT
|
||||
@Carikod AS Cari_Kod,
|
||||
/* =========================================================
|
||||
✅ Devir Satırı (kur bazında) — Opening'den gelir
|
||||
Hareket varsa: startdate öncesi
|
||||
Hareket yoksa: enddate itibariyle bakiye
|
||||
========================================================= */
|
||||
SELECT
|
||||
o.Cari_Kod,
|
||||
COALESCE(
|
||||
(SELECT TOP 1 cd.CurrAccDescription
|
||||
FROM CurrDesc cd
|
||||
@@ -167,38 +180,31 @@ SELECT
|
||||
ORDER BY cd.CurrAccCode)
|
||||
) AS Cari_Isim,
|
||||
|
||||
CONVERT(varchar(10), @startdate, 23),
|
||||
CONVERT(varchar(10), @startdate, 23),
|
||||
CONVERT(varchar(10), @startdate, 23) AS Belge_Tarihi,
|
||||
CONVERT(varchar(10), @startdate, 23) AS Vade_Tarihi,
|
||||
|
||||
'Baslangic_devir',
|
||||
'Devir',
|
||||
'Devir Bakiyesi',
|
||||
'Baslangic_devir' AS Belge_No,
|
||||
'Devir' AS Islem_Tipi,
|
||||
'Devir Bakiyesi' AS Aciklama,
|
||||
|
||||
obc.Para_Birimi,
|
||||
o.Para_Birimi,
|
||||
|
||||
CASE
|
||||
WHEN obc.Devir_Bakiyesi >= 0 THEN obc.Devir_Bakiyesi
|
||||
ELSE 0
|
||||
END,
|
||||
CASE WHEN o.Devir_Bakiyesi >= 0 THEN o.Devir_Bakiyesi ELSE 0 END AS Borc,
|
||||
CASE WHEN o.Devir_Bakiyesi < 0 THEN ABS(o.Devir_Bakiyesi) ELSE 0 END AS Alacak,
|
||||
|
||||
CASE
|
||||
WHEN obc.Devir_Bakiyesi < 0 THEN ABS(obc.Devir_Bakiyesi)
|
||||
ELSE 0
|
||||
END,
|
||||
|
||||
obc.Devir_Bakiyesi,
|
||||
o.Devir_Bakiyesi AS Bakiye,
|
||||
|
||||
CAST(NULL AS varchar(32)) AS Parislemler
|
||||
|
||||
FROM OpeningByCurrency obc
|
||||
FROM Opening o
|
||||
|
||||
|
||||
ORDER BY
|
||||
Para_Birimi,
|
||||
ORDER BY
|
||||
Para_Birimi,
|
||||
Belge_Tarihi;
|
||||
`,
|
||||
parislemFilter,
|
||||
parislemFilter,
|
||||
parislemFilter, // HasMovement
|
||||
parislemFilter, // Opening
|
||||
parislemFilter, // Movements
|
||||
)
|
||||
|
||||
rows, err := db.MssqlDB.Query(query,
|
||||
|
||||
@@ -234,11 +234,18 @@ func (r *UserRepository) GetLegacyUserForLogin(login string) (*models.User, erro
|
||||
COALESCE(u.upass,'') as upass,
|
||||
u.is_active,
|
||||
COALESCE(u.email,''),
|
||||
COALESCE(u.dfrole_id,0) as role_id,
|
||||
COALESCE(ru.dfrole_id,0) as role_id,
|
||||
COALESCE(dr.code,'') as role_code,
|
||||
COALESCE(u.force_password_change,false)
|
||||
FROM dfusr u
|
||||
LEFT JOIN dfrole dr ON dr.id = u.dfrole_id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT dfrole_id
|
||||
FROM dfrole_usr
|
||||
WHERE dfusr_id = u.id
|
||||
ORDER BY dfrole_id
|
||||
LIMIT 1
|
||||
) ru ON true
|
||||
LEFT JOIN dfrole dr ON dr.id = ru.dfrole_id
|
||||
WHERE u.is_active = true
|
||||
AND (
|
||||
LOWER(u.code) = LOWER($1)
|
||||
|
||||
@@ -62,7 +62,7 @@ func AdminResetPasswordHandler(db *sql.DB) http.HandlerFunc {
|
||||
// ---------------------------------------------------
|
||||
// 4️⃣ UPDATE mk_dfusr
|
||||
// ---------------------------------------------------
|
||||
_, err = db.Exec(`
|
||||
res, err := db.Exec(`
|
||||
UPDATE mk_dfusr
|
||||
SET
|
||||
password_hash = $1,
|
||||
@@ -77,6 +77,24 @@ func AdminResetPasswordHandler(db *sql.DB) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
affected, _ := res.RowsAffected()
|
||||
if affected == 0 {
|
||||
_, err = db.Exec(`
|
||||
UPDATE dfusr
|
||||
SET
|
||||
upass = $1,
|
||||
force_password_change = true,
|
||||
last_updated_date = NOW()
|
||||
WHERE id = $2
|
||||
AND is_active = true
|
||||
`, string(hash), userID)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "legacy password reset failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
// 5️⃣ REFRESH TOKEN REVOKE
|
||||
// ---------------------------------------------------
|
||||
|
||||
@@ -30,6 +30,12 @@ type LoginRequest struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func looksLikeBcryptHash(value string) bool {
|
||||
return strings.HasPrefix(value, "$2a$") ||
|
||||
strings.HasPrefix(value, "$2b$") ||
|
||||
strings.HasPrefix(value, "$2y$")
|
||||
}
|
||||
|
||||
func ensureLegacyUserReadyForSession(db *sql.DB, legacyUser *models.User) (int64, error) {
|
||||
desiredID := int64(legacyUser.ID)
|
||||
|
||||
@@ -148,19 +154,36 @@ func LoginHandler(db *sql.DB) http.HandlerFunc {
|
||||
if err == nil {
|
||||
|
||||
// mk_dfusr authoritative
|
||||
if strings.TrimSpace(mkUser.PasswordHash) != "" {
|
||||
mkHash := strings.TrimSpace(mkUser.PasswordHash)
|
||||
if mkHash != "" {
|
||||
if looksLikeBcryptHash(mkHash) {
|
||||
cmpErr := bcrypt.CompareHashAndPassword(
|
||||
[]byte(mkHash),
|
||||
[]byte(pass),
|
||||
)
|
||||
if cmpErr == nil {
|
||||
_ = mkRepo.TouchLastLogin(mkUser.ID)
|
||||
writeLoginResponse(w, db, mkUser)
|
||||
return
|
||||
}
|
||||
|
||||
if bcrypt.CompareHashAndPassword(
|
||||
[]byte(mkUser.PasswordHash),
|
||||
[]byte(pass),
|
||||
) != nil {
|
||||
http.Error(w, "Kullanıcı adı veya parola hatalı", http.StatusUnauthorized)
|
||||
return
|
||||
if !mkUser.ForcePasswordChange {
|
||||
http.Error(w, "invalid credentials", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf(
|
||||
"LOGIN FALLBACK legacy allowed (force_password_change=true) username=%s id=%d",
|
||||
mkUser.Username,
|
||||
mkUser.ID,
|
||||
)
|
||||
} else {
|
||||
log.Printf(
|
||||
"LOGIN FALLBACK legacy allowed (non-bcrypt mk hash) username=%s id=%d",
|
||||
mkUser.Username,
|
||||
mkUser.ID,
|
||||
)
|
||||
}
|
||||
|
||||
_ = mkRepo.TouchLastLogin(mkUser.ID)
|
||||
writeLoginResponse(w, db, mkUser)
|
||||
return
|
||||
}
|
||||
// password_hash boşsa legacy fallback
|
||||
} else if err != repository.ErrMkUserNotFound {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type IdTitleOption struct {
|
||||
@@ -57,7 +58,7 @@ type RoleDepartmentPermissionHandler struct {
|
||||
func NewRoleDepartmentPermissionHandler(db *sql.DB) *RoleDepartmentPermissionHandler {
|
||||
|
||||
return &RoleDepartmentPermissionHandler{
|
||||
DB: db, // ✅ EKLENDİ
|
||||
DB: db, // Added
|
||||
Repo: permissions.NewRoleDepartmentPermissionRepo(db),
|
||||
}
|
||||
}
|
||||
@@ -417,7 +418,7 @@ func (h *PermissionHandler) GetUserOverrides(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
list, err := h.Repo.GetUserOverridesByUserID(userID)
|
||||
if err != nil {
|
||||
log.Println("❌ USER OVERRIDE LOAD ERROR:", err)
|
||||
log.Println("USER OVERRIDE LOAD ERROR:", err)
|
||||
http.Error(w, "db error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -425,6 +426,138 @@ func (h *PermissionHandler) GetUserOverrides(w http.ResponseWriter, r *http.Requ
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
_ = json.NewEncoder(w).Encode(list)
|
||||
}
|
||||
|
||||
type routePermissionSeed struct {
|
||||
Module string
|
||||
Action string
|
||||
Path string
|
||||
}
|
||||
|
||||
type moduleActionSeed struct {
|
||||
Module string
|
||||
Action string
|
||||
}
|
||||
|
||||
type permissionSnapshot struct {
|
||||
user map[string]bool
|
||||
roleDept map[string]bool
|
||||
role map[string]bool
|
||||
}
|
||||
|
||||
func permissionKey(module, action string) string {
|
||||
return module + "|" + action
|
||||
}
|
||||
|
||||
func loadPermissionSnapshot(
|
||||
db *sql.DB,
|
||||
userID int64,
|
||||
roleID int64,
|
||||
deptCodes []string,
|
||||
) (permissionSnapshot, error) {
|
||||
snapshot := permissionSnapshot{
|
||||
user: make(map[string]bool, 128),
|
||||
roleDept: make(map[string]bool, 128),
|
||||
role: make(map[string]bool, 128),
|
||||
}
|
||||
|
||||
userRows, err := db.Query(`
|
||||
SELECT module_code, action, allowed
|
||||
FROM mk_sys_user_permissions
|
||||
WHERE user_id = $1
|
||||
`, userID)
|
||||
if err != nil {
|
||||
return snapshot, err
|
||||
}
|
||||
for userRows.Next() {
|
||||
var module, action string
|
||||
var allowed bool
|
||||
if err := userRows.Scan(&module, &action, &allowed); err != nil {
|
||||
_ = userRows.Close()
|
||||
return snapshot, err
|
||||
}
|
||||
snapshot.user[permissionKey(module, action)] = allowed
|
||||
}
|
||||
if err := userRows.Err(); err != nil {
|
||||
_ = userRows.Close()
|
||||
return snapshot, err
|
||||
}
|
||||
_ = userRows.Close()
|
||||
|
||||
if len(deptCodes) > 0 {
|
||||
roleDeptRows, err := db.Query(`
|
||||
SELECT module_code, action, BOOL_OR(allowed) AS allowed
|
||||
FROM vw_role_dept_permissions
|
||||
WHERE role_id = $1
|
||||
AND department_code = ANY($2)
|
||||
GROUP BY module_code, action
|
||||
`,
|
||||
roleID,
|
||||
pq.Array(deptCodes),
|
||||
)
|
||||
if err != nil {
|
||||
return snapshot, err
|
||||
}
|
||||
for roleDeptRows.Next() {
|
||||
var module, action string
|
||||
var allowed bool
|
||||
if err := roleDeptRows.Scan(&module, &action, &allowed); err != nil {
|
||||
_ = roleDeptRows.Close()
|
||||
return snapshot, err
|
||||
}
|
||||
snapshot.roleDept[permissionKey(module, action)] = allowed
|
||||
}
|
||||
if err := roleDeptRows.Err(); err != nil {
|
||||
_ = roleDeptRows.Close()
|
||||
return snapshot, err
|
||||
}
|
||||
_ = roleDeptRows.Close()
|
||||
}
|
||||
|
||||
roleRows, err := db.Query(`
|
||||
SELECT module_code, action, allowed
|
||||
FROM mk_sys_role_permissions
|
||||
WHERE role_id = $1
|
||||
`, roleID)
|
||||
if err != nil {
|
||||
return snapshot, err
|
||||
}
|
||||
for roleRows.Next() {
|
||||
var module, action string
|
||||
var allowed bool
|
||||
if err := roleRows.Scan(&module, &action, &allowed); err != nil {
|
||||
_ = roleRows.Close()
|
||||
return snapshot, err
|
||||
}
|
||||
snapshot.role[permissionKey(module, action)] = allowed
|
||||
}
|
||||
if err := roleRows.Err(); err != nil {
|
||||
_ = roleRows.Close()
|
||||
return snapshot, err
|
||||
}
|
||||
_ = roleRows.Close()
|
||||
|
||||
return snapshot, nil
|
||||
}
|
||||
|
||||
func resolvePermissionFromSnapshot(
|
||||
s permissionSnapshot,
|
||||
module string,
|
||||
action string,
|
||||
) bool {
|
||||
key := permissionKey(module, action)
|
||||
|
||||
if allowed, ok := s.user[key]; ok {
|
||||
return allowed
|
||||
}
|
||||
if allowed, ok := s.roleDept[key]; ok {
|
||||
return allowed
|
||||
}
|
||||
if allowed, ok := s.role[key]; ok {
|
||||
return allowed
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetUserRoutePermissionsHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -436,10 +569,16 @@ func GetUserRoutePermissionsHandler(db *sql.DB) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
repo := permissions.NewPermissionRepository(db)
|
||||
|
||||
// JWT’den departmanlar
|
||||
depts := claims.DepartmentCodes
|
||||
snapshot, err := loadPermissionSnapshot(
|
||||
db,
|
||||
int64(claims.ID),
|
||||
int64(claims.RoleID),
|
||||
claims.DepartmentCodes,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := db.Query(`
|
||||
SELECT DISTINCT
|
||||
@@ -454,17 +593,9 @@ func GetUserRoutePermissionsHandler(db *sql.DB) http.HandlerFunc {
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
type Row struct {
|
||||
Route string `json:"route"`
|
||||
CanAccess bool `json:"can_access"`
|
||||
}
|
||||
|
||||
list := make([]Row, 0, 64)
|
||||
|
||||
routeSeeds := make([]routePermissionSeed, 0, 128)
|
||||
for rows.Next() {
|
||||
|
||||
var module, action, path string
|
||||
|
||||
if err := rows.Scan(
|
||||
&module,
|
||||
&action,
|
||||
@@ -473,22 +604,26 @@ func GetUserRoutePermissionsHandler(db *sql.DB) http.HandlerFunc {
|
||||
continue
|
||||
}
|
||||
|
||||
allowed, err := repo.ResolvePermissionChain(
|
||||
int64(claims.ID),
|
||||
int64(claims.RoleID),
|
||||
depts,
|
||||
module,
|
||||
action,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Println("PERM RESOLVE ERROR:", err)
|
||||
continue
|
||||
}
|
||||
routeSeeds = append(routeSeeds, routePermissionSeed{
|
||||
Module: module,
|
||||
Action: action,
|
||||
Path: path,
|
||||
})
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]Row, 0, len(routeSeeds))
|
||||
for _, route := range routeSeeds {
|
||||
list = append(list, Row{
|
||||
Route: path,
|
||||
CanAccess: allowed,
|
||||
Route: route.Path,
|
||||
CanAccess: resolvePermissionFromSnapshot(
|
||||
snapshot,
|
||||
route.Module,
|
||||
route.Action,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -507,18 +642,17 @@ func GetMyEffectivePermissions(db *sql.DB) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
repo := permissions.NewPermissionRepository(db)
|
||||
|
||||
// ✅ JWT'DEN DEPARTMENTS
|
||||
depts := claims.DepartmentCodes
|
||||
|
||||
log.Printf("🧪 EFFECTIVE PERM | user=%d role=%d depts=%v",
|
||||
claims.ID,
|
||||
claims.RoleID,
|
||||
depts,
|
||||
snapshot, err := loadPermissionSnapshot(
|
||||
db,
|
||||
int64(claims.ID),
|
||||
int64(claims.RoleID),
|
||||
claims.DepartmentCodes,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
// all system perms
|
||||
all, err := db.Query(`
|
||||
SELECT DISTINCT module_code, action
|
||||
FROM mk_sys_routes
|
||||
@@ -529,14 +663,7 @@ func GetMyEffectivePermissions(db *sql.DB) http.HandlerFunc {
|
||||
}
|
||||
defer all.Close()
|
||||
|
||||
type Row struct {
|
||||
Module string `json:"module"`
|
||||
Action string `json:"action"`
|
||||
Allowed bool `json:"allowed"`
|
||||
}
|
||||
|
||||
list := make([]Row, 0, 128)
|
||||
|
||||
moduleActions := make([]moduleActionSeed, 0, 128)
|
||||
for all.Next() {
|
||||
|
||||
var m, a string
|
||||
@@ -544,22 +671,32 @@ func GetMyEffectivePermissions(db *sql.DB) http.HandlerFunc {
|
||||
continue
|
||||
}
|
||||
|
||||
allowed, err := repo.ResolvePermissionChain(
|
||||
int64(claims.ID),
|
||||
int64(claims.RoleID),
|
||||
depts,
|
||||
m,
|
||||
a,
|
||||
)
|
||||
moduleActions = append(moduleActions, moduleActionSeed{
|
||||
Module: m,
|
||||
Action: a,
|
||||
})
|
||||
}
|
||||
if err := all.Err(); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
type Row struct {
|
||||
Module string `json:"module"`
|
||||
Action string `json:"action"`
|
||||
Allowed bool `json:"allowed"`
|
||||
}
|
||||
|
||||
list := make([]Row, 0, len(moduleActions))
|
||||
for _, item := range moduleActions {
|
||||
list = append(list, Row{
|
||||
Module: m,
|
||||
Action: a,
|
||||
Allowed: allowed,
|
||||
Module: item.Module,
|
||||
Action: item.Action,
|
||||
Allowed: resolvePermissionFromSnapshot(
|
||||
snapshot,
|
||||
item.Module,
|
||||
item.Action,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@ package services
|
||||
|
||||
import (
|
||||
"bssapp-backend/models"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
@@ -16,7 +20,6 @@ func CheckPasswordWithLegacy(user *models.User, plain string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
plain = strings.TrimSpace(plain)
|
||||
if plain == "" {
|
||||
return false
|
||||
}
|
||||
@@ -28,11 +31,100 @@ func CheckPasswordWithLegacy(user *models.User, plain string) bool {
|
||||
|
||||
// 1️⃣ bcrypt hash mi?
|
||||
if isBcryptHash(stored) {
|
||||
return bcrypt.CompareHashAndPassword([]byte(stored), []byte(plain)) == nil
|
||||
candidates := make([]string, 0, 10)
|
||||
seen := map[string]struct{}{}
|
||||
add := func(v string) {
|
||||
if v == "" {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[v]; ok {
|
||||
return
|
||||
}
|
||||
seen[v] = struct{}{}
|
||||
candidates = append(candidates, v)
|
||||
}
|
||||
|
||||
add(plain)
|
||||
trimmed := strings.TrimSpace(plain)
|
||||
add(trimmed)
|
||||
|
||||
bases := append([]string(nil), candidates...)
|
||||
for _, base := range bases {
|
||||
md5Sum := md5.Sum([]byte(base))
|
||||
md5Hex := hex.EncodeToString(md5Sum[:])
|
||||
add(md5Hex)
|
||||
add(strings.ToUpper(md5Hex))
|
||||
|
||||
sha1Sum := sha1.Sum([]byte(base))
|
||||
sha1Hex := hex.EncodeToString(sha1Sum[:])
|
||||
add(sha1Hex)
|
||||
add(strings.ToUpper(sha1Hex))
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for _, candidate := range candidates {
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(stored), []byte(candidate)); err == nil {
|
||||
return true
|
||||
} else {
|
||||
lastErr = err
|
||||
}
|
||||
if encoded, ok := encodeLegacySingleByte(candidate); ok {
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(stored), encoded); err == nil {
|
||||
return true
|
||||
} else {
|
||||
lastErr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
if lastErr != nil {
|
||||
log.Printf(
|
||||
"LEGACY BCRYPT MISMATCH stored_len=%d candidates=%d last_err=%v",
|
||||
len(stored),
|
||||
len(candidates),
|
||||
lastErr,
|
||||
)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 2️⃣ TAM LEGACY — düz metin (eski kayıtlar)
|
||||
return stored == plain
|
||||
if stored == plain {
|
||||
return true
|
||||
}
|
||||
trimmed := strings.TrimSpace(plain)
|
||||
if trimmed != plain && trimmed != "" && stored == trimmed {
|
||||
return true
|
||||
}
|
||||
|
||||
// 3️⃣ Legacy hash variants seen in old dfusr.upass data.
|
||||
if isHexDigest(stored, 32) {
|
||||
sumRaw := md5.Sum([]byte(plain))
|
||||
if strings.EqualFold(stored, hex.EncodeToString(sumRaw[:])) {
|
||||
return true
|
||||
}
|
||||
if trimmed != plain && trimmed != "" {
|
||||
sumTrim := md5.Sum([]byte(trimmed))
|
||||
if strings.EqualFold(stored, hex.EncodeToString(sumTrim[:])) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isHexDigest(stored, 40) {
|
||||
sumRaw := sha1.Sum([]byte(plain))
|
||||
if strings.EqualFold(stored, hex.EncodeToString(sumRaw[:])) {
|
||||
return true
|
||||
}
|
||||
if trimmed != plain && trimmed != "" {
|
||||
sumTrim := sha1.Sum([]byte(trimmed))
|
||||
if strings.EqualFold(stored, hex.EncodeToString(sumTrim[:])) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isBcryptHash(s string) bool {
|
||||
@@ -40,3 +132,46 @@ func isBcryptHash(s string) bool {
|
||||
strings.HasPrefix(s, "$2b$") ||
|
||||
strings.HasPrefix(s, "$2y$")
|
||||
}
|
||||
|
||||
func isHexDigest(s string, expectedLen int) bool {
|
||||
if len(s) != expectedLen {
|
||||
return false
|
||||
}
|
||||
for _, r := range s {
|
||||
if (r < '0' || r > '9') &&
|
||||
(r < 'a' || r > 'f') &&
|
||||
(r < 'A' || r > 'F') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// encodeLegacySingleByte converts text to a Turkish-compatible single-byte
|
||||
// representation (similar to Windows-1254 / ISO-8859-9) for legacy bcrypt data.
|
||||
func encodeLegacySingleByte(s string) ([]byte, bool) {
|
||||
out := make([]byte, 0, len(s))
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case 'Ğ':
|
||||
out = append(out, 0xD0)
|
||||
case 'ğ':
|
||||
out = append(out, 0xF0)
|
||||
case 'İ':
|
||||
out = append(out, 0xDD)
|
||||
case 'ı':
|
||||
out = append(out, 0xFD)
|
||||
case 'Ş':
|
||||
out = append(out, 0xDE)
|
||||
case 'ş':
|
||||
out = append(out, 0xFE)
|
||||
default:
|
||||
if r >= 0 && r <= 0xFF {
|
||||
out = append(out, byte(r))
|
||||
} else {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user