Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-02-19 01:34:36 +03:00
parent 7184a40dd3
commit 0136e6638b
49 changed files with 616 additions and 672 deletions

33
svc/.env.local Normal file
View 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

View File

@@ -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 {

View File

@@ -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 .envden 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()

View File

@@ -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 Openingden */
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,

View File

@@ -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)

View File

@@ -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
// ---------------------------------------------------

View File

@@ -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 {

View File

@@ -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)
// JWTden 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,
),
})
}

View File

@@ -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
}