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
- Variables
- func AutoCreateEnabled(ctx context.Context, db *store.Store) bool
- func CanResolveByUsername(src GroupSource) bool
- func ConfirmTTL(ctx context.Context, db *store.Store) time.Duration
- func DeleteTTL(ctx context.Context, db *store.Store) time.Duration
- func HashPassword(password string) (string, error)
- func InAdminGroup(ctx context.Context, db *store.Store, groups []string) (bool, error)
- func NewLocalSecret() (secret, verifier string, err error)
- func NormalizeSecret(in string) (string, error)
- func OIDCManagedInDB(ctx context.Context, db *store.Store) bool
- func ParseAccountDuration(raw string) (time.Duration, error)
- func ParsePasswordMinLength(v string) (int, error)
- func SetupCompleted(ctx context.Context, db *store.Store) bool
- func VerifyLocalSecret(verifier, input string) bool
- func VerifyPassword(verifier, input string) bool
- type Attempt
- type AuthRequest
- type ClaimGroups
- type GroupPresence
- type GroupResult
- type GroupSource
- type Identity
- type LoginSource
- type Logins
- func (l *Logins) Challenge(state string) (code, sourceAddr string, ok bool)
- func (l *Logins) Confirm(state, userCode string) error
- func (l *Logins) Drop(a *Attempt)
- func (l *Logins) DropState(state string)
- func (l *Logins) Lookup(state string) (AuthRequest, bool)
- func (l *Logins) Park(state string, id Identity) error
- func (l *Logins) SetMaxPending(n int)
- func (l *Logins) Start(sourceAddr string) (*Attempt, error)
- type OIDC
- type OIDCConfig
- type OIDCHolder
- type PasswordPolicy
- type StoredOIDC
- type SubjectResolver
- type SwitchableGroupSource
- func (s *SwitchableGroupSource) Groups(ctx context.Context, id Identity) (GroupResult, error)
- func (s *SwitchableGroupSource) GroupsBySubject(ctx context.Context, subject string) (GroupResult, error)
- func (s *SwitchableGroupSource) ResolvesByUsername() bool
- func (s *SwitchableGroupSource) Set(src GroupSource)
- type UsernameResolver
- type WebSessions
- func (w *WebSessions) Age(token string) (time.Duration, error)
- func (w *WebSessions) Create(username, accountID string) (string, error)
- func (w *WebSessions) CreateLocal(username, accountID string) (string, error)
- func (w *WebSessions) Destroy(token string)
- func (w *WebSessions) DestroyAccount(accountID string) int
- func (w *WebSessions) DestroyUser(username string) int
- func (w *WebSessions) ResolveSessionFull(token string) (username, accountID string, viaLocal bool, err error)
Constants ¶
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" )
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.
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.
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.
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.
const KeyLoginSource = "auth.source"
KeyLoginSource, ayarlar tablosundaki anahtar.
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.
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 ¶
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") )
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") )
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.
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".
var ErrOIDCNotConfigured = errors.New("auth: oidc is not configured in the database")
ErrOIDCNotConfigured: veritabanında OIDC ayarı yok.
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.
var ErrUnknownSource = errors.New("auth: unknown login source")
ErrUnknownSource: tanınmayan kaynak adı.
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.
var OIDCSecretKeys = map[string]bool{KeyOIDCClientSecret: true}
OIDCSecretKeys, şifrelenerek saklanması gereken OIDC ayarları.
Functions ¶
func AutoCreateEnabled ¶
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 ¶
ConfirmTTL, hesabın doğrulanmadan kalabileceği süre. 0 = KAPALI.
func HashPassword ¶
* 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 ¶
* 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 ¶
* 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 ¶
* 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 ¶
OIDCManagedInDB, ayarların veritabanına taşınmış olduğu.
func ParseAccountDuration ¶
* 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 ¶
* 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 ¶
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 ¶
* 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 ¶
* 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.
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 ¶
func (ClaimGroups) Groups(_ context.Context, id Identity) (GroupResult, error)
⚠️ 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 ¶
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 ¶
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) DropState ¶
* 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) SetMaxPending ¶
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ı.
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 (*OIDC) Begin ¶
func (o *OIDC) Begin() (AuthRequest, 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) 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ı.
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 (s *SwitchableGroupSource) Groups(ctx context.Context, id Identity) (GroupResult, error)
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 (*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.