auth

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package auth implements postern's web-side authentication: OIDC login (S3.2), the out-of-band SSH flow that uses it (S3.3) and TOTP (S5).

Index

Constants

View Source
const (
	KeyOIDCIssuer   = "oidc.issuer_url"
	KeyOIDCClientID = "oidc.client_id"
	// #nosec G101 -- kimlik bilgisi değil, settings tablosunun ANAHTAR adı
	KeyOIDCClientSecret = "oidc.client_secret"

	/*
	 * KeyOIDCManaged, "bu kurulumun OIDC ayarları ARTIK veritabanında".
	 *
	 * ⚠️ Var olma sebebi yükseltme günü. Config dosyasında oidc.* olan
	 * bir kurulum, bu işaret konana kadar DOSYAYI kullanmaya devam
	 * ediyor: aksi hâlde yükseltme, çalışan bir OIDC kurulumunu boş
	 * veritabanı satırlarıyla sessizce kapatırdı.
	 *
	 * İşaret bir kez konduğunda dosya bir daha okunmuyor — iki kaynak
	 * arasında "hangisi kazanır" sorusunu her okumada yeniden sormak,
	 * cevabın zamanla kaymasına açık kapı bırakırdı.
	 */
	KeyOIDCManaged = "oidc.managed_in_db"

	/*
	 * KeyOIDCGroupsClaim, grup adlarını taşıyan claim.
	 *
	 * ⚠️ ALAN VARDI, AYARI YOKTU. OIDCConfig.GroupsClaim ilk günden
	 * beri duruyor ve yorumu Entra'nın "roles", bazı kurulumların
	 * "memberOf" kullandığını söylüyor — ama onu dolduran hiçbir yol
	 * yazılmamıştı, yani pratikte "groups" sabitti. Entra ya da Okta
	 * kullanan bir kurum hiç grup göremiyordu ve sebebini de göremiyordu:
	 * ekranda ayarlanacak bir şey yoktu.
	 */
	KeyOIDCGroupsClaim = "oidc.groups_claim"

	/*
	 * KeyOIDCScopes, yetkilendirme isteğinde istenen kapsamlar.
	 *
	 * ⚠️ SABİTTİ ve eksikti: "openid email". `profile` YOKTU, oysa
	 * preferred_username çoğu sağlayıcıda tam olarak orada yaşıyor —
	 * yani postern kullanıcı adını isteyip istemediğini söylemeden
	 * bekliyordu ve gelmeyince e-posta eşleştirmesine düşüyordu.
	 * Okta ve Auth0 ayrıca grupları vermek için AÇIK bir kapsam
	 * istiyor; onu ekleyecek bir yer de yoktu.
	 */
	KeyOIDCScopes = "oidc.scopes"
)
View Source
const (
	KeyConfirmTTL = "auth.confirm_ttl"
	KeyDeleteTTL  = "auth.delete_after"

	// DefaultConfirmTTL, doğrulanmayan hesabın pasifleşme süresi.
	DefaultConfirmTTL = 45 * 24 * time.Hour
	// DefaultDeleteTTL, pasif hesabın 'deleted' işaretlenme süresi.
	DefaultDeleteTTL = 180 * 24 * time.Hour
)

* Zaman temelli hesap iptalinin ayarları. * * ⚠️ NEDEN ZAMAN: OIDC'de kaynağa "bu kişi hâlâ var mı" diye * SORULAMIYOR — bir claim ancak kullanıcı giriş yaparken geliyor. Yani * IdP'de kapatılmış bir hesap, kişi bir daha hiç girmezse postern'de * süresiz ayakta kalıyor. Elimizdeki tek ölçüt, kaynağın o kişiyi en * son ne zaman doğruladığı. * * ⚠️ VARSAYILANLAR CÖMERT VE BU KASITLI. Bu sayaç "işten ayrıldı"yı * değil "bir süredir kendini kanıtlamadı"yı ölçüyor; üç haftalık bir * tatil yanlış pozitif üretiyor. Bedeli düşük tutan şey, pasifleşmenin * TEK BİR GİRİŞLE geri alınması — ve kaynak kişiyi gerçekten kapattıysa * o giriş zaten olmuyor.

View Source
const DefaultOIDCScopes = "openid email profile"

DefaultOIDCScopes, kapsam ayarlanmadığında istenenler.

⚠️ profile BURADA: preferred_username onun içinde geliyor ve postern'in kimlik yolunun tamamı o claim'e dayanıyor.

View Source
const KeyAdminGroup = "ldap.admin_group"

* KeyAdminGroup, YÖNETİCİ yetkisi veren grubun adı. * * ⚠️ Burada, ldap paketinde DEĞİL: grup adı OIDC claim'inden de * gelebiliyor ve iki kaynağın paylaştığı bir kavramı birinin paketine * koymak, diğerini o pakete bağımlı kılıyordu. * * ⚠️ Bu grubu ele geçiren yalnızca rol almıyor: panele giriyor, yani * DENETİM GÜNLÜĞÜNÜ ve OTURUM KAYITLARINI da okuyor. Rol almaktan * farklı bir şey — geçmişe erişim.

View Source
const KeyAutoCreate = "auth.auto_create"

* KeyAutoCreate, kimliği doğrulanan ama postern hesabı olmayan kişi * için hesabın KENDİLİĞİNDEN açılıp açılmayacağı. * * ⚠️ VARSAYILAN KAPALI ve bu bir güvenlik kararı. "IdP'de hesabın * olması postern'de hesabın olması demek değil" kuralı ürünün * başından beri yazılı; açık varsayılan onu sessizce tersine * çevirirdi. Kapalıyken kapı da kapanmıyor: kişi onay kuyruğuna * düşüyor ve bunu ekranda görüyor.

View Source
const KeyLoginSource = "auth.source"

KeyLoginSource, ayarlar tablosundaki anahtar.

View Source
const KeyPasswordMinLength = "password.min_length"

Politika ayarları. TEK AYARLANABİLİR DÜĞME uzunluk.

⚠️ "Büyük harf + rakam + sembol şart" kuralı BİLEREK YOK. Ölçülen sonucu şu: insanlar "Parola1!" yazıyor ve kural sağlanmış oluyor. Kuralın ürettiği şey tahmin edilebilir bir kalıp, entropi değil. Uzunluk + çeşitlilik tabanı + yaygın parola listesi aynı işi, kalıp üretmeden yapıyor. Düğmeyi eklemek isteyen olursa gerekçesini burada bu paragrafın karşısına yazsın.

View Source
const KeySetupCompleted = "setup.completed_at"

* KeySetupCompleted, kurulum sihirbazının TAMAMLANDIĞI an. * * ⚠️ VAR OLMA SEBEBİ: sihirbaz "isteğe bağlı bir ekran" olarak * bırakıldığında atlanıyordu, ve atlandığında geriye kaynağı * seçilmemiş — yani kapısı config dosyasından TÜRETİLEN — bir kurulum * kalıyordu. Ürünün en kritik kararı, keşfedilmeyi bekleyen bir menü * maddesi olamaz. * * Zaman damgası, boolean değil: "ne zaman kuruldu" sorusunun cevabı * denetim açısından bedava geliyor.

Variables

View Source
var (
	// ErrStateMismatch: callback'teki state, bu girişim için üretilenle
	// aynı değil. Bu bir CSRF işaretidir — Warpgate'in CVE-2026-44347'si
	// tam olarak bu kontrolün yokluğuydu: saldırgan kendi login'inin
	// callback'ini kurbanın oturumuna yedirebiliyordu.
	ErrStateMismatch = errors.New("auth: state mismatch")

	// ErrNonceMismatch: ID token'daki nonce beklenenle aynı değil —
	// token bu giriş DENEMESİ için kesilmemiş (tekrar oynatma işareti).
	ErrNonceMismatch = errors.New("auth: nonce mismatch")
)
View Source
var (
	// ErrLoginDenied: giriş denemesi onaylanmadan yakıldı — yanlış
	// güvenlik kodu, süre aşımı ya da tekrar kullanım girişimi.
	ErrLoginDenied = errors.New("auth: login denied")

	// ErrUnknownAttempt: bu state'e ait bekleyen bir deneme yok (hiç
	// olmadı, süresi doldu ya da çoktan sonuçlandı).
	ErrUnknownAttempt = errors.New("auth: unknown login attempt")

	// ErrNotReady: kod girildi ama tarayıcı tarafı henüz tamamlanmadı.
	// Denemeyi YAKMAZ — kullanıcı tekrar deneyebilir.
	ErrNotReady = errors.New("auth: browser sign-in is not finished yet")

	// ErrTooManyPending: aynı anda çok fazla giriş onay bekliyor.
	//
	// Kuyruğa almak yerine REDDETMEK, varsayılan-reddet kuralının
	// gereği: kuyruğa alınan bir deneme, tutulan bir goroutine demek —
	// yani tam olarak paylaştırmaya çalıştığımız kaynak.
	ErrTooManyPending = errors.New("auth: too many pending logins")
)
View Source
var ErrMalformedSecret = errors.New("auth: value is not shaped like a postern secret")

ErrMalformedSecret, girilen değerin postern'in ÜRETEBİLECEĞİ bir biçimde olmadığını söyler.

View Source
var ErrNoSession = errors.New("auth: no such web session")

ErrNoSession: token tanınmıyor — hiç olmadı, süresi doldu ya da çıkış yapıldı. Üçü de dışarıya aynı görünür: "giriş yap".

View Source
var ErrOIDCNotConfigured = errors.New("auth: oidc is not configured in the database")

ErrOIDCNotConfigured: veritabanında OIDC ayarı yok.

View Source
var ErrPolicyBound = fmt.Errorf(
	"password.min_length must be a whole number between %d and %d",
	passwordMinFloor, passwordMaxLength)

ErrPolicyBound, ayarın taban/tavan dışında olduğunu söyler.

View Source
var ErrUnknownSource = errors.New("auth: unknown login source")

ErrUnknownSource: tanınmayan kaynak adı.

View Source
var ErrWeakPassword = errors.New("password does not meet the policy")

* ErrWeakPassword, parolanın politikayı geçmediğini söyler. * * ⚠️ PAKET ÖNEKİ ("auth: ") BİLEREK YOK. Bu paketteki diğer sentinel'ler * onu taşıyor ve doğru: onlar günlüğe ve operatöre gidiyor, nereden * geldiklerini söylemeleri gerekiyor. Bu metin ise doğrudan * KULLANICININ EKRANINA basılıyor — parolasını değiştirmeye çalışan * kişiye "auth: password does not meet the policy" demek, ona hiçbir * şey anlatmayan bir uygulama detayını göstermek.

View Source
var OIDCSecretKeys = map[string]bool{KeyOIDCClientSecret: true}

OIDCSecretKeys, şifrelenerek saklanması gereken OIDC ayarları.

Functions

func AutoCreateEnabled

func AutoCreateEnabled(ctx context.Context, db *store.Store) bool

AutoCreateEnabled, hesapların kendiliğinden açılıp açılmadığı.

Okunamayan ya da bozuk değer KAPALI sayılıyor: bir veritabanı arızası, hesap açan bir kapıyı kendiliğinden açmamalı.

func CanResolveByUsername

func CanResolveByUsername(src GroupSource) bool

CanResolveByUsername, kaynağa adla sorulup sorulamayacağı. Arayüzü uygulamayan kaynak SORULAMAZ sayılır — varsayılan, bilmediğimiz bir kaynağa güvenmemek.

func ConfirmTTL

func ConfirmTTL(ctx context.Context, db *store.Store) time.Duration

ConfirmTTL, hesabın doğrulanmadan kalabileceği süre. 0 = KAPALI.

func DeleteTTL

func DeleteTTL(ctx context.Context, db *store.Store) time.Duration

DeleteTTL, pasif hesabın silinmiş işaretlenme süresi. 0 = KAPALI.

func HashPassword

func HashPassword(password string) (string, error)

* HashPassword, kullanıcı seçimi bir parolanın doğrulayıcısını üretir. * * ⚠️ BİÇİM KONTROLÜ YOK ve olmamalı: kontrol edilecek bir biçim yok. * Bu, localcred.go'daki NormalizeSecret'ın TAM TERSİ ve iki yolun neden * birbirine karışmaması gerektiğinin de sebebi. Politika kontrolü * ÇAĞIRANIN işi — burada yapılsaydı, politika sıkılaştığında var olan * parolaların yeniden hash'lenmesi imkânsız hâle gelirdi. * * KDF aynı: argon2id, localcred.go'daki parametrelerle. Orada maliyet * "gücü için gerekli değil ama ileride biri parola derse" diye * seçilmişti. O gün geldi ve parametreler zaten yerindeydi.

func InAdminGroup

func InAdminGroup(ctx context.Context, db *store.Store, groups []string) (bool, error)

* InAdminGroup, verilen grup listesinin yönetici grubunu içerip * içermediğini söyler. * * ⚠️ YALNIZCA GERÇEKTEN ÇÖZÜLMÜŞ gruplarla çağrılmalı. "Bulamadım" ya * da "cevap veremedim" hâlinde boş bir listeyle çağrılırsa cevap * "hayır" olur — ki bu güvenli taraf, ama çağıranın o ayrımı zaten * yapmış olması gerekir. * * Karşılaştırma harf duyarsız: dizinler grup adlarını öyle sayıyor ve * "SysAdmins" yazan bir ayarın "sysadmins" grubunu görmemesi sessiz bir * yetki kaybı olurdu.

func NewLocalSecret

func NewLocalSecret() (secret, verifier string, err error)

* NewLocalSecret, yeni bir sır ve onun doğrulayıcısını üretir. * * Dönen ilk değer operatöre BİR KEZ gösterilecek olan; ikincisi * veritabanına yazılacak olan. Sır hiçbir yerde saklanmıyor.

func NormalizeSecret

func NormalizeSecret(in string) (string, error)

* NormalizeSecret, girilen değeri karşılaştırılabilir hâle getirir. * * ⚠️ BİÇİM KONTROLÜ BİR GÜVENLİK ÖZELLİĞİ, kolaylık değil. postern'in * üretemeyeceği bir değer KDF'e hiç ulaşmıyor. Bunun somut faydası: * kutucuğa kurumsal parolasını yazan operatörün parolası hiçbir zaman * hash'lenmiyor, hiçbir zaman bir karşılaştırmaya girmiyor ve hiçbir * biçimde saklanmıyor — yalnızca reddediliyor.

func OIDCManagedInDB

func OIDCManagedInDB(ctx context.Context, db *store.Store) bool

OIDCManagedInDB, ayarların veritabanına taşınmış olduğu.

func ParseAccountDuration

func ParseAccountDuration(raw string) (time.Duration, error)

* ParseAccountDuration, süre ayarını çözer — GÜN dahil. * * ⚠️ time.ParseDuration "d" BİLMİYOR ve bu buradaki ölçekte gerçek bir * tuzak: bu ayarların doğal birimi gün ve operatör "45d" yazıyor. * Ölçüldü — "45d" kaydediliyor, çözümlenemiyor, sessizce varsayılana * düşüyor ve operatör yazdığının anlaşıldığını sanıyor. En kötü hâli * "365d" yazıp korumanın 45 günde çalıştığını fark etmemek. * * "0" KAPALI demek ve bilinçli bir karar; onun dışında çözümlenemeyen * değer HATA — yazma yolları bunu reddedip operatöre söylüyor.

func ParsePasswordMinLength

func ParsePasswordMinLength(v string) (int, error)

* ParsePasswordMinLength, ayar değerini çözer. * * ⚠️ YAZMA ANINDA ÇAĞRILIYOR, okuma anında değil. Çözülemeyen değer * okuma anında varsayılana düşerdi (güvenli yön) ama operatöre hiçbir * şey söylemezdi: "8" yerine "sekiz" yazıp politikanın 12'de kaldığını * fark etmemek mümkün olurdu. auth.ParseAccountDuration'daki karar.

func SetupCompleted

func SetupCompleted(ctx context.Context, db *store.Store) bool

SetupCompleted, kurulum sihirbazının tamamlanmış olduğu.

Okunamayan değer TAMAMLANMAMIŞ sayılıyor: bir veritabanı arızasında sihirbazı göstermek, kurulmamış bir sistemi kurulmuş sanmaktan zararsız.

func VerifyLocalSecret

func VerifyLocalSecret(verifier, input string) bool

* VerifyLocalSecret, girilen değerin doğrulayıcıya uyup uymadığı. * * ⚠️ SABİT ZAMANLI KARŞILAŞTIRMA. Doğrulayıcı bir hash olsa da, * baytları erken biten bir karşılaştırma saldırgana adım adım geri * bildirim verir.

func VerifyPassword

func VerifyPassword(verifier, input string) bool

* VerifyPassword, parolayı doğrulayıcıyla karşılaştırır. * * ⚠️ VerifyLocalSecret'tan AYRI BİR FONKSİYON, aynı fonksiyona bayrak * eklenmiş hâli değil. Sebebi somut: tek fonksiyon olsaydı, çağıranın * bayrağı unutması ya da saldırganın bayrağı seçebilmesi hâlinde * kurumsal parola, biçim kontrolüne takılmadan doğrudan KDF'e giderdi. * İki ayrı isim, çağrı yerinde hangi kapının açıldığını görünür yapıyor.

Types

type Attempt

type Attempt struct {
	// URL, kullanıcının tarayıcıda açacağı adres (AuthRequest.URL).
	URL string

	// UserCode, TARAYICIDA gösterilen ve TERMİNALE yazılan doğrulama kodu.
	//
	// ⚠️ YÖN ÖNEMLİ ve bir güvenlik açığının düzeltilmesidir. Eskiden
	// ters yöndeydi: kod terminale basılıyor, tarayıcıya yazılıyordu.
	// O düzenin savunduğunu sandığı saldırı şuydu — saldırgan kendi
	// giriş linkini kurbana yollar, kurban tamamlar, saldırganın
	// bekleyen SSH oturumu kurbanın kimliğiyle açılır. Ama kod bunu
	// ENGELLEMİYORDU: terminali gören zaten SALDIRGANIN KENDİSİ, yani
	// kodu da o biliyordu ve linkle birlikte kurbana yolluyordu.
	// (Ölçüldü: TestAttackOOBDeviceCodePhishing saldırıyı uçtan uca
	// gerçekleştiriyordu.)
	//
	// Ters yönde saldırganın kodu YOK: kod yalnızca kurbanın tarayıcı
	// ekranında beliriyor. Saldırının işlemesi için kurbanın kodu
	// okuyup saldırgana GÖNDERMESİ gerekiyor — tek tıklık bir onay
	// yerine, insanların alarma geçtiği bir istek. Microsoft'un push
	// bildirimlerine "numara eşleştirme" eklemesinin sebebiyle aynı.
	UserCode string

	// SourceAddr, SSH bağlantısının geldiği adres. Tarayıcıda
	// GÖSTERİLİYOR: kurbanın "bunu ben başlatmadım" diyebilmesinin tek
	// somut dayanağı bu.
	SourceAddr string
	// contains filtered or unexported fields
}

Attempt, tek bir OOB giriş denemesinin SSH tarafındaki ucu.

func (*Attempt) State

func (a *Attempt) State() string

State, denemenin anahtarı. SSH tarafı Confirm'e vermek için istiyor; alanın kendisi dışa kapalı kalıyor ki kimse haritayı elle kurcalamasın.

func (*Attempt) Wait

func (a *Attempt) Wait(ctx context.Context) (Identity, error)

type AuthRequest

type AuthRequest struct {
	// State, callback'i BU denemeye bağlayan CSRF değeri. URL'ye gider,
	// callback'te geri gelir, Exchange'de karşılaştırılır.
	State string

	// Nonce, ID token'ı bu denemeye bağlayan değer. URL'ye gider,
	// sağlayıcı token'ın İÇİNE koyar, VerifyIDToken karşılaştırır.
	// State kanalı korur, nonce token'ı korur — ikisi aynı şey değil.
	Nonce string

	// Verifier, PKCE'nin sırrı. URL'ye SHA-256 özeti gider
	// (code_challenge), token isteğinde kendisi gider. Code'u çalan
	// biri verifier olmadan token alamaz.
	Verifier string

	// URL, kullanıcının tarayıcıda açacağı yetkilendirme adresi.
	URL string

	/*
	 * Gen, bu isteği ÜRETEN sağlayıcı kuşağı.
	 *
	 * ⚠️ Tamamlanma anında karşılaştırılmak zorunda. Ayarlar akışın
	 * ortasında değiştirilirse, A sağlayıcısının ürettiği code B'nin
	 * token ucuna gönderilirdi — code ve istemci sırrı, operatörün az
	 * önce yazdığı adrese giderdi. Eşleşmeyen kuşak, akışın
	 * reddedilmesi demek.
	 */
	Gen uint64
}

AuthRequest, TEK bir giriş denemesinin durumu.

Begin üretir, çağıran saklar (S3.3'te bekleyen SSH bağlantısının yanında), Exchange tüketir. TEK KULLANIMLIKTIR: başarılı ya da başarısız, bir Exchange'den sonra atılmalı — aynı state'in ikinci kez kabulü, tekrar oynatmaya kapı açar. (Saklama ve süre aşımı S3.3'ün işi; bu paket yalnızca protokolü bilir.)

type ClaimGroups

type ClaimGroups struct{}

ClaimGroups, grupları kimliğin kendisinden okur — yani ID token'dan. LDAP yapılandırılmadığında kullanılan varsayılan kaynak.

func (ClaimGroups) Groups

⚠️ CLAIM KAYNAĞI HER ZAMAN "PRESENT". Kimlik belirteci kullanıcının KENDİSİNDEN geliyor ve IdP onu az önce doğruladı; "bu kişi kaynakta yok" diye bir hâl yok. Boş grup listesi burada gerçek bir cevaptır.

type GroupPresence

type GroupPresence int

* GroupPresence, kaynağın kullanıcı hakkında ne SÖYLEYEBİLDİĞİ. * * ⚠️ ÜÇ DEĞER, ÇÜNKÜ İKİSİ YETMİYOR. Eskiden bu arayüz düz bir dilim * döndürüyordu ve boş dilim iki ayrı cevabı birden taşıyordu: "kullanıcı * dizinde var, hiçbir grupta değil" ile "kullanıcı dizinde HİÇ YOK". * * Birincisi gerçek bir cevap ve yetkinin bitmesi demek. İkincisi çoğu * zaman bir yapılandırma hatası: IdP kullanıcıyı "yigit" biliyor, dizinde * kayıt "yigit.basalma". Ölçüldü — ikisi aynı yola düşünce giriş anında * ProvisionUser → SyncRoles bütün SSO rollerini siliyordu; ne bekleme * süresi, ne tavan, ne uyarı. Senkronizasyon döngüsündeki patlama * yarıçapı korumalarının hiçbiri bu yolda yok.

const (
	// GroupsUnknown: kaynak cevap VEREMEDİ. Hiçbir şey çıkarılamaz ve
	// hiçbir yetki kararı verilemez. Sıfır değer bilerek bu: cevabı
	// doldurmayı unutan bir kod yolu, yetki silen tarafa değil
	// "bilmiyorum" tarafına düşer.
	GroupsUnknown GroupPresence = iota
	// GroupsPresent: kaynak cevap verdi ve kullanıcıyı TANIYOR. Gruplar
	// güvenilir; boş olması "hiçbir gruba üye değil" demektir.
	GroupsPresent
	// GroupsAbsent: kaynak BAŞARIYLA cevap verdi ve kullanıcıyı
	// tanımıyor. Bu "yetkisi yok" DEĞİL, "soruyu yanlış sorduk ya da
	// kişi burada değil" demek; kararı korumalı senkronizasyon yolu
	// verir, giriş yolu değil.
	GroupsAbsent
)

func (GroupPresence) String

func (p GroupPresence) String() string

type GroupResult

type GroupResult struct {
	Presence GroupPresence
	Groups   []string

	/*
	 * Disabled, kaynağın bu hesabı KAPATTIĞINI söylemesi.
	 *
	 * ⚠️ GroupsPresent İLE BİRLİKTE GELİR ve gelmesi normaldir: bir
	 * hesabı devre dışı bırakmak dizinde girişi silmez, grup
	 * üyeliklerini de kaldırmaz. Yalnızca Presence'a bakan bir çağıran,
	 * işten ayrılmada ve olay müdahalesinde atılan İLK adımı görmezden
	 * gelmiş olur.
	 */
	Disabled       bool
	DisabledReason string
}

GroupResult, bir grup sorgusunun cevabı.

type GroupSource

type GroupSource interface {
	Groups(ctx context.Context, id Identity) (GroupResult, error)
}

GroupSource, kullanıcının grup üyeliklerini veren kaynak.

İki gerçekleştirmesi var ve ikisi de aynı şeyi üretir — bir grup adı listesi. Senkronizasyon kodu hangisinden geldiğini BİLMEZ; policy'nin model.User'a bakıp kaynağı bilmemesiyle aynı desen, bir kat yukarıda.

  • ClaimGroups: grupları ID token'ın claim'inden okur (S5.2).
  • ldap.Source: LDAP dizinine sorar (S5.3).

LDAP'ın kazandırdığı şey tazelik: token'daki claim giriş anında dondurulmuştur, LDAP ise her sorguda güncel cevap verir.

type Identity

type Identity struct {
	// Issuer ve Subject BİRLİKTE kimliğin KALICI anahtarıdır ("iss" +
	// "sub"). OIDC'de sub sağlayıcı içinde yeniden atanmaz.
	//
	// ⚠️ EŞLEŞTİRME BUNLARLA YAPILIR, Username ile DEĞİL. Username
	// preferred_username claim'inden geliyor ve birçok sağlayıcıda
	// kullanıcının kendi değiştirebildiği bir alan; onunla eşleştirmek,
	// adını değiştirebilen herkese var olan bir postern hesabını
	// (is_admin bayrağı dahil) devretmek demekti.
	Issuer  string
	Subject string

	// Email, eşleştirmede kullanılacak adres. yalnızca email_verified
	// true ise doldurulur — doğrulanmamış e-posta, sahibi olmayan bir
	// iddiadır ve kimlik eşleştirmesinde kullanılamaz.
	Email string

	// Username, IdP'nin verdiği kullanıcı adı (preferred_username).
	// JIT sağlamada hem postern kullanıcı adı hem hedeflerdeki hesap adı
	// olacak — kurumsal ortamda "isim.soyisim".
	Username string

	// Groups, IdP'nin bildirdiği grup adları. Hangi claim'den okunacağı
	// yapılandırılabilir (OIDCConfig.GroupsClaim).
	Groups []string
}

Identity, doğrulanmış bir ID token'dan çıkan kimlik.

S3.3 bunu postern kullanıcısına e-posta üzerinden bağlayacak (users.email — o sütun ilk günden bunun için vardı).

type LoginSource

type LoginSource string

* LoginSource, panel girişinin hangi kaynağa sorulduğu. * * ⚠️ NEDEN TEK DEĞER, İKİ BOOLEAN DEĞİL. * * "OIDC etkin" ve "LDAP etkin" diye iki bayrak, ikisinin birden açık * olduğu bir durumu TEMSİL EDİLEBİLİR yapardı. O durumun anlamı yok — * ama var olduğu anda, kural her yazma yolunda tekrarlanan bir kontrole * dönüşür: CLI'dan doğrudan yazan biri, iki isteğin yarışı, ya da * kontrolü eklemeyi unutan yeni bir uç onu bozar. Tek değerli bir * ayarda o durum hiç yoktur; kural şemanın kendisidir. * * SSH kapısı BUNA BAĞLI DEĞİL ve olmamalı: orada kimlik anahtarla * kanıtlanıyor ve panel kaynağını değiştirmek kimsenin sunucu erişimini * kesmemeli. Bu ayar yalnızca PANELİN kapısını seçiyor.

const (
	// SourceLocal, postern'in kendi kimlik bilgileri. Hiçbir dizin
	// kaynağı seçilmediğinde geçerli olan geri dönüş yolu — ve acil
	// çıkış: dizin arızalanırsa host'tan buraya dönülür.
	SourceLocal LoginSource = "local"

	// SourceOIDC, kimlik sağlayıcı üzerinden tarayıcı akışı.
	SourceOIDC LoginSource = "oidc"

	// SourceLDAP, dizin kullanıcı adı + KURUMSAL parola.
	SourceLDAP LoginSource = "ldap"
)

func ActiveLoginSource

func ActiveLoginSource(ctx context.Context, db *store.Store, oidcConfigured bool) (src LoginSource, stored bool, err error)

* ActiveLoginSource, saklanan kaynağı okur; saklanmamışsa TÜRETİR. * * ⚠️ TÜRETME, YÜKSELTMEYİ BOZMAMAK İÇİN. Bu ayar yokken kurulmuş bir * dağıtımda OIDC yapılandırılmışsa kapı zaten OIDC'ydi; körlemesine * "local" demek, çalışan bir kurulumu yükseltmenin kimsenin giremediği * bir panele dönüşmesi demekti. * * stored=false döndüğünde arayüz bunu SÖYLEMELİ: "seçilmedi, config * dosyasından türetildi" ile "seçildi" aynı şey değil. * * ⚠️ OKUMA HATASI YUTULMUYOR. Bir veritabanı arızasında varsayılana * düşmek, kapalı olması gereken bir kapıyı açardı; çağıran hatayı görüp * kapalı tarafta kalmalı.

func ParseLoginSource

func ParseLoginSource(v string) (LoginSource, error)

ParseLoginSource, metni kaynağa çevirir.

Tanımadığını REDDEDİYOR, bir varsayılana düşmüyor: yazım hatası olan bir ayarın sessizce yerel kapıyı açması, tam olarak kapatılmak istenen kapıyı açmak olurdu.

type Logins

type Logins struct {
	// contains filtered or unexported fields
}

Logins, bekleyen OOB giriş denemelerinin kaydı.

İki dünyayı buluşturur: SSH tarafı bir Attempt başlatıp Wait ile bekler; HTTP tarafı (tarayıcı callback'i) Lookup → [Exchange] → Park → Confirm zinciriyle kimliği teslim eder. Exchange'in kendisi BURADA DEĞİL: bu tip yalnızca eşzamanlılık ve yaşam döngüsü bilir, ağ bilmez — birim testleri de bu sayede IdP'siz koşar.

YAŞAM DÖNGÜSÜ — her deneme bu çizgide tek yönlü ilerler:

Start ──► bekliyor ──Park──► kimlik parkta ──Confirm──► teslim ✓
             │                    │
          (timeout/Drop)      (yanlış kod / ikinci Park)
             ▼                    ▼
           yandı ✗              yandı ✗

Yanan deneme geri dönmez: aynı state ile ikinci bir şans yok. Tekrar oynatmanın (replay) panzehiri sürecin kendisini tek kullanımlık yapmak.

func NewLogins

func NewLogins(o *OIDCHolder) *Logins

func (*Logins) Challenge

func (l *Logins) Challenge(state string) (code, sourceAddr string, ok bool)

Challenge, tarayıcıda gösterilecek kodu ve SSH kaynağını döner.

Kod yalnızca kimlik PARK EDİLDİKTEN sonra veriliyor: aksi hâlde state'i bilen herkes (yani denemeyi başlatan saldırgan) kodu doğrudan çekebilirdi ve yön değişikliği hiçbir işe yaramazdı.

func (*Logins) Confirm

func (l *Logins) Confirm(state, userCode string) error

Confirm, terminalden gelen kodu doğrular ve kimliği teslim eder.

⚠️ ÇAĞIRAN ARTIK SSH TARAFI. Eskiden tarayıcı çağırıyordu; yön değişikliğinin gerekçesi UserCode'un doküman yorumunda.

func (*Logins) Drop

func (l *Logins) Drop(a *Attempt)

func (*Logins) DropState

func (l *Logins) DropState(state string)

* DropState, state ile bilinen bir denemeyi düşürür. * * ⚠️ VAR OLMA SEBEBİ: callback tarafında elde *Attempt yok, yalnızca * state var. Denemeyi düşürmeden dönmek, SSH tarafındaki kullanıcıyı * zaman aşımına kadar (varsayılan dakikalar) hiçbir şey söylemeden * bekletirdi — oysa cevabı zaten biliyoruz. * * Bilinmeyen state sessizce yok sayılıyor: zaten bitmiş bir denemeyi * ikinci kez düşürmek bir hata değil.

func (*Logins) Lookup

func (l *Logins) Lookup(state string) (AuthRequest, bool)

func (*Logins) Park

func (l *Logins) Park(state string, id Identity) error

func (*Logins) SetMaxPending

func (l *Logins) SetMaxPending(n int)

SetMaxPending, aynı anda onay bekleyebilecek giriş sayısını sınırlar. 0 = sınırsız. Dinlemeye başlamadan ÖNCE çağrılmalı.

Sınırın BURADA olması gerekiyor, sshd'de değil: bekleyen girişleri tutan harita bu nesnenin içinde ve HTTP kapısı (Lookup/Park/Confirm) aynı haritayı okuyor. sshd'de uygulanan bir sınır, iki yazardan yalnızca birini sınırlardı.

func (*Logins) Start

func (l *Logins) Start(sourceAddr string) (*Attempt, error)

Start, yeni bir tarayıcı giriş denemesi açar.

sourceAddr, SSH bağlantısının kaynak adresi; onay sayfasında gösteriliyor.

type OIDC

type OIDC struct {
	// contains filtered or unexported fields
}

OIDC, tek bir sağlayıcıya karşı giriş akışı yürütür.

Alanlar NewOIDC'de dolduruluyor; sonrasında salt okunur — aynı OIDC değeri eşzamanlı akışlarca paylaşılabilir.

func NewOIDC

func NewOIDC(ctx context.Context, cfg OIDCConfig) (*OIDC, error)

func (*OIDC) Begin

func (o *OIDC) Begin() (AuthRequest, error)

func (*OIDC) Exchange

func (o *OIDC) Exchange(ctx context.Context, req AuthRequest, gotState, code string) (Identity, error)

func (*OIDC) VerifyIDToken

func (o *OIDC) VerifyIDToken(ctx context.Context, raw, expectedNonce string) (Identity, error)

type OIDCConfig

type OIDCConfig struct {
	// IssuerURL, sağlayıcının kök adresi. Discovery belgesi
	// <issuer>/.well-known/openid-configuration adresinden okunur; token
	// içindeki "iss" claim'i de BİREBİR bununla karşılaştırılır.
	IssuerURL string

	// ClientID, postern'in sağlayıcıdaki kaydı. ID token'ın "aud"
	// claim'i bu olmak zorunda — go-oidc doğruluyor.
	ClientID string

	// ClientSecret boş olabilir: CLI/OOB akışında postern "public
	// client"tır, sırrı yoktur; onun yerini PKCE tutar.
	ClientSecret string

	// RedirectURL, sağlayıcının code'u geri getireceği adres. Sağlayıcı
	// tarafında kayıtlı olanla birebir aynı olmalı.
	RedirectURL string

	// GroupsClaim, grup adlarını taşıyan claim. Boşsa "groups".
	//
	// YAPILANDIRILABİLİR olması bilinçli: Warpgate'in sabit
	// "warpgate_roles" adı, insanların gruplarını "groups" claim'ine
	// koyup hiçbir eşleşme görmemesinin ve sebebini bulamamasının
	// kaynağı (issue #1283). Her IdP farklı ad kullanır: Keycloak
	// "groups", Entra "roles", bazı kurulumlar "memberOf".
	GroupsClaim string

	/*
	 * Scopes, yetkilendirme isteğinde istenen kapsamlar (boşlukla
	 * ayrılmış). Boşsa DefaultOIDCScopes.
	 *
	 * ⚠️ SABİT DEĞİL, çünkü sağlayıcılar aynı şeyi aynı kapsamla
	 * vermiyor: Okta ve Auth0 grupları ancak açıkça istenirse
	 * gönderiyor. Sabit bir liste, o kurulumlarda postern'i grupsuz
	 * bırakıyordu ve sebebi hiçbir ekranda görünmüyordu.
	 */
	Scopes string
}

OIDCConfig, bir OpenID Connect sağlayıcısına bağlanmak için gerekenler.

type OIDCHolder

type OIDCHolder struct {
	// contains filtered or unexported fields
}

* OIDCHolder, kimlik sağlayıcı istemcisini ÇALIŞIRKEN değiştirilebilir * kılar. * * ⚠️ NEDEN GEREKLİ: OIDC ayarları config dosyasından veritabanına * taşındı ve panelden düzenleniyor. Süreci yeniden başlatmadan * uygulanamasalardı, ürünün "kurulumdan sonra sunucuya hiç girme" * hedefi sihirbazın tam ortasında kırılırdı. * * ⚠️ İKİ AYRI DURUM, VE KARIŞTIRILAMAZLAR: * * configured — bir yapılandırma VAR (ayar satırı ya da config dosyası) * live — o yapılandırmadan ÇALIŞAN bir istemci kurulabildi * * Sağlayıcı ulaşılamazken configured true, live false olur. Bu ayrım * arayüzün "OIDC seçilemiyor" derken sebebini söyleyebilmesinin tek * yolu: "hiç ayarlanmamış" ile "ayarlı ama şu an cevap vermiyor" aynı * ekranı hak etmiyor.

func NewOIDCHolder

func NewOIDCHolder() *OIDCHolder

func (*OIDCHolder) Clear

func (h *OIDCHolder) Clear()

* Clear, çalışan istemciyi DÜŞÜRÜR — yapılandırmanın varlığına * dokunmadan. * * ⚠️ AYARLAR YAZILDIĞI ANDA, HERHANGİ BİR AĞ ÇAĞRISINDAN ÖNCE * çağrılmalı. Yeni sağlayıcı kurulamazsa sonuç "eski istemci hâlâ * ayakta" değil, "yapılandırma değişti, henüz çalışmıyor" olmalı: * yoksa operatör istemci sırrını değiştirdikten sonra ESKİ sırla * girişlerin sürdüğünü görür ve iptal ettiğini sanır.

func (*OIDCHolder) Configured

func (h *OIDCHolder) Configured() bool

Configured, bir yapılandırma var mı — istemci kurulamamış olsa bile.

func (*OIDCHolder) Current

func (h *OIDCHolder) Current() (*OIDC, uint64)

* Current, ŞU ANKİ istemciyi ve KUŞAK numarasını birlikte döner. * * ⚠️ İKİSİ BİRLİKTE DÖNMEK ZORUNDA ve çağıran ikinci kez OKUMAMALI. * Bir giriş akışı A sağlayıcısında başlayıp B'de tamamlanırsa, A'nın * ürettiği code B'nin token ucuna gönderilir — yani code ve istemci * sırrı, operatörün az önce yazdığı yeni adrese sızar. Kuşak numarası * bu akışı reddetmek için var; ayrı ayrı okunan bir istemci/kuşak * çifti o korumayı boşa çıkarırdı.

func (*OIDCHolder) Install

func (h *OIDCHolder) Install(o *OIDC)

Install, yeni istemciyi yürürlüğe koyar ve kuşağı ilerletir.

func (*OIDCHolder) Live

func (h *OIDCHolder) Live() bool

Live, çalışan bir istemci var mı.

func (*OIDCHolder) SetConfigured

func (h *OIDCHolder) SetConfigured(on bool)

SetConfigured, yapılandırmanın VARLIĞINI işaretler.

type PasswordPolicy

type PasswordPolicy struct {
	MinLength int `json:"min_length"`
	MaxLength int `json:"max_length"`
	// MinDistinct, farklı karakter sayısı tabanı.
	MinDistinct int `json:"min_distinct"`
}

PasswordPolicy, bir parolanın geçmesi gereken kurallar.

Yalnızca MinLength ayarlanabilir; gerisi taban ve kapatılamaz. Yapı yine de bütün kuralları taşıyor: panelin kullanıcıya "kural neydi" diye gösterebilmesi için tek kaynak burası olmalı, ekranda ikinci bir kopyası değil.

func DefaultPasswordPolicy

func DefaultPasswordPolicy() PasswordPolicy

DefaultPasswordPolicy, ayar okunamadığında geçerli olan.

func LoadPasswordPolicy

func LoadPasswordPolicy(ctx context.Context, db *store.Store) PasswordPolicy

* LoadPasswordPolicy, politikayı ayarlardan okur. * * ⚠️ HATA VARSAYILANA DÜŞÜYOR, kapıyı kapatmıyor. Bu bir yetkilendirme * kararı değil, bir kalite eşiği: veritabanı okunamadığı için hiç kimse * parolasını değiştiremesin demek, arızayı bir kilitlenmeye çevirirdi. * Yön güvenli: düşülen değer varsayılan, gevşek değil.

func (PasswordPolicy) Check

func (p PasswordPolicy) Check(password, username string) error

* Check, parolayı politikaya göre sınar. * * ⚠️ SEBEP AÇIKÇA DÖNÜYOR ve bu bir sızıntı DEĞİL: kişi zaten kendi * seçtiği değeri biliyor. Gizlenecek bir şey yok, gizlemenin bedeli ise * "reddedildi, neden bilmiyorum" döngüsü. * * username BOŞ GEÇİLEBİLİR; verildiğinde parola onu içeremez.

type StoredOIDC

type StoredOIDC struct {
	IssuerURL    string
	ClientID     string
	ClientSecret string
	// GroupsClaim boşsa "groups" kullanılıyor (oidc.go).
	GroupsClaim string
	// Scopes boşsa DefaultOIDCScopes kullanılıyor.
	Scopes string
}

StoredOIDC, veritabanındaki OIDC ayarları.

func LoadOIDC

func LoadOIDC(ctx context.Context, db *store.Store) (StoredOIDC, error)

* LoadOIDC, veritabanındaki OIDC ayarlarını okur. * * ⚠️ EKSİK ALAN HATA. Yarım bir yapılandırmadan istemci kurmaya * çalışmak, sağlayıcıya anlamsız bir istek atıp "ulaşılamıyor" demek * olurdu — oysa sorun ağda değil, ayarda.

type SubjectResolver

type SubjectResolver interface {
	GroupsBySubject(ctx context.Context, subject string) (GroupResult, error)
}

Set, kaynağı değiştirir. Süregelen sorgular eski kaynakla biter; sonrakiler yenisini kullanır. ResolvesByUsername, o ANDAKİ kaynağın yeteneğini bildirir. Panelden kaynak değiştiğinde cevap da değişiyor — yakalanmış bir "evet", dizinden claim'e dönen bir kurulumda yanlış olurdu.

  • SubjectResolver, grupları KULLANICI ADIYLA DEĞİL kararlı kimlikle
  • çözebilen kaynak. *
  • ⚠️ Var olma sebebi: adla arama, dizinde YENİDEN ADLANDIRILAN kişiyi
  • SİLİNMİŞ kişiden ayırt edemiyor — ikisi de "yok" döner ve çağıranlar
  • onu erişim iptaline çevirir. Claim tabanlı kaynaklar bunu
  • uygulayamaz (bir claim'e "şu subject kim" diye sorulamaz), o yüzden
  • arayüz isteğe bağlı.

type SwitchableGroupSource

type SwitchableGroupSource struct {
	// contains filtered or unexported fields
}

SwitchableGroupSource, çalışırken değiştirilebilen grup kaynağı.

NEDEN GEREKLİ: LDAP ayarları panelden değiştirilebiliyor, ama grup kaynağını SSH tarafı (sshd.Server) ve web tarafı (httpapi.Server) ayrı ayrı tutuyor. İkisine ayrı nesne verseydik, panelden yapılan bir değişiklik yalnızca web'e işler ve SSH'tan giren kullanıcı eski kaynaktan yetki alırdı — "iki kapı, tek gerçek" kuralının tam karşıtı. Bu sarmalayıcıyı ikisi de paylaşıyor: Set bir kez çağrılır, her iki kapı da yeni kaynağı görür.

func NewSwitchableGroupSource

func NewSwitchableGroupSource(initial GroupSource) *SwitchableGroupSource

NewSwitchableGroupSource, verilen kaynakla başlar.

func (*SwitchableGroupSource) Groups

func (*SwitchableGroupSource) GroupsBySubject

func (s *SwitchableGroupSource) GroupsBySubject(ctx context.Context, subject string) (GroupResult, error)

GroupsBySubject, sarmalanan kaynak destekliyorsa ona devreder.

Desteklemiyorsa GroupsUnknown: "bilmiyorum" demek, "grubu yok" demekten her zaman güvenli taraf.

func (*SwitchableGroupSource) ResolvesByUsername

func (s *SwitchableGroupSource) ResolvesByUsername() bool

func (*SwitchableGroupSource) Set

func (s *SwitchableGroupSource) Set(src GroupSource)

type UsernameResolver

type UsernameResolver interface {
	ResolvesByUsername() bool
}

* UsernameResolver, kullanıcıyı YALNIZCA ADIYLA çözebilen kaynak. * * ⚠️ AYRIM HAYATİ. Grupları token'dan okuyan kaynak (ClaimGroups) bunu * yapamaz: elinde bir belirteç yokken sorulacak bir şey yoktur. Oysa * anahtarla açılan bir SSH oturumunda token YOK — yalnızca postern'in * kendi kaydındaki kullanıcı adı var. * * Bu ayrım olmadan tazeleme şöyle patlıyordu: boş bir kimlikle sorulan * ClaimGroups "present, hiç grup yok" diye cevap veriyor ve çağıran onu * gerçek bir cevap sanıp bütün SSO rollerini siliyor. Yani tazeleme * niyetiyle yazılan kod, tam da bu oturumda iki kez düzelttiğimiz * "bilgisizliği cevap sanmak" hatasını üçüncü kez yapardı.

type WebSessions

type WebSessions struct {
	// contains filtered or unexported fields
}

WebSessions, tarayıcı oturumlarının bellek içi kaydı.

BELLEKTE olması bilinçli: bastion yeniden başlarsa herkes yeniden giriş yapar. Bu bir kayıp değil, özellik — süreç yenilendiğinde eski token evreni de yenilenir ve DB'de temizlenecek oturum çöpü birikmez.

func NewWebSessions

func NewWebSessions() *WebSessions

NewWebSessions, boş bir kayıt döner.

func (*WebSessions) Age

func (w *WebSessions) Age(token string) (time.Duration, error)

* Age, oturumun ne kadar önce açıldığını döner. * * ⚠️ NEDEN VAR: bir oturumun VAR OLMASI ile kişinin AZ ÖNCE kimliğini * kanıtlamış olması aynı şey değil. 12 saat yaşayan bir belirteci * çalan biri, hesabın sahibi kadar "giriş yapmış" görünüyor. İkinci * faktör bağlamak gibi kalıcı sonuçlu adımlar taze bir kanıt istiyor * ve tazeliğin ölçüsü burası. * * Oturum yoksa hata döner — "bilinmiyor"u "çok taze" diye okutmamak * için.

func (*WebSessions) Create

func (w *WebSessions) Create(username, accountID string) (string, error)

Create, kullanıcı için yeni bir oturum açar ve token'ı döner.

Token cookie'de yaşayacak ve oturumun KENDİSİ o: 32 bayt crypto/rand, tahmin edilebilirse oturum çalınır.

func (*WebSessions) CreateLocal

func (w *WebSessions) CreateLocal(username, accountID string) (string, error)

CreateLocal, YEREL PAROLA KAPISINDAN açılan oturum. Ayrı bir isim, çünkü köken çağrı yerinde görünmeli: bayrağı unutan bir kapı, zorunlu parola değişikliğini sessizce atlatırdı.

func (*WebSessions) Destroy

func (w *WebSessions) Destroy(token string)

Destroy, oturumu düşürür (logout). Olmayan token için sessiz no-op: çifte logout bir hata değil.

func (*WebSessions) DestroyAccount

func (w *WebSessions) DestroyAccount(accountID string) int

* DestroyAccount, verilen HESAP KİMLİĞİNE bağlı oturumları düşürür. * * ⚠️ NEDEN AD DEĞİL KİMLİK — VE ADLA DÜŞÜRÜLDÜĞÜ HÂLİN BEDELİ. * * accountStillOpen bir oturumu KİMLİĞE bakarak reddediyor ama sonra * ADLA düşürüyordu. Ad serbest bırakıldıktan sonra iki taraf aynı şeyi * göstermiyor: ayrılan kişinin uyuyan sekmesinden gelen bir istek * reddediliyor (doğru), ama düşürme, adı DEVRALAN yeni kişinin canlı * oturumunu da siliyordu — yeni çalışan iş ortasında, mesajsız, denetim * defterinde açıklaması olmayan bir şekilde dışarı atılıyordu. Log * satırı da hesabı "gitti" diye yazıyordu: eski satır için doğru, az * önce öldürdüğü oturum için yanlış. * * DestroyUser diğer çağıranlarında (purge, silme, parola değişikliği) * DOĞRU kalıyor: orada özne gerçekten ADIN kendisi.

func (*WebSessions) DestroyUser

func (w *WebSessions) DestroyUser(username string) int

* DestroyUser, bir kullanıcının BÜTÜN oturumlarını düşürür. * * ⚠️ PAROLA DEĞİŞİKLİĞİNİN AMACI BU. Parolasının ele geçtiğini düşünen * kişi onu değiştirir; eski parolayla açılmış oturumlar ayakta kalırsa * değiştirmek hiçbir işe yaramaz — saldırgan zaten içeride ve 12 saat * daha içeride kalır. Kimlik bilgisi iptal edildiğinde de aynısı * geçerli. * * Dönen sayı düşen oturum sayısı: denetim kaydına yazılıyor, çünkü * "kaç oturum kapandı" operatörün soracağı ilk soru. * * ⚠️ EŞLEŞME HARF DUYARSIZ — VERİTABANI DA ÖYLE. * * ÖLÇÜLEN ARIZA: burada tam eşleşme aranıyordu, oysa store kullanıcı * adını harf duyarsız çözüyor (dialect.go: users.username). Yani * `DELETE /api/admin/users/YIGIT` satırı gerçekten siliyor ama * "yigit" ile açılmış oturumlar ayakta kalıyordu; aynı ad yeniden * yaratıldığında o oturum YENİ kişiye çözülüyordu — üretildi. * * Fazladan bir hesabı düşürme riski yok: iki hesap yalnızca harf * yazımıyla ayrışamıyor, çünkü benzersizlik kısıtı da aynı harf * duyarsızlığı kullanıyor. İki kapı aynı adı aynı şekilde çözmezse, * arada geçen her şey sessizce kaçıyor.

func (*WebSessions) ResolveSessionFull

func (w *WebSessions) ResolveSessionFull(token string) (username, accountID string, viaLocal bool, err error)

* ResolveSessionFull, belirteci çözer: ad, oturumun bağlı olduğu hesap * KİMLİĞİ, ve belirteci yerel parola kapısının üretip üretmediği. * Tanınmayan ya da süresi dolmuş belirteç için ErrNoSession. * * Karşılaştırma map anahtarıyla; sabit-zaman gerekmez — belirteç * rastgele 43 karakter, map lookup'ının zamanlaması içerik sızdırmaz * (güvenlik kodundaki durum farklıydı: orada iki KISA, tahmin * edilebilir değer yan yana geliyordu). * * ⚠️ TEK AÇIK ÇÖZÜCÜ BU. Yanında Resolve (yalnızca ad) ve * ResolveSession (ad + köken) duruyordu; oturum ara katmanı buraya * geçtiğinden beri ikisinin de test dışında çağıranı kalmamıştı. * Yazılıp çağrılmayan kod bu depoda bir kusur, ve bir oturum * çözücüsünde ayrıca tehlikeli: hesap kimliğini DÖNDÜRMEYEN bir * sarmalayıcı, bir sonraki çağıranı ada bakmaya davet ediyor — adın * güvenli bir tutamak olmadığını bu dosyanın kendisi anlatırken.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL