Dokümanlar menüsü
Açık-çekirdek auth (roleAuth)
Ücretsiz, opt-in kimlik katmanı: bearer ya da basic token üstünde dört sınıf (superAdmin / admin / client / viewer). Ne kadar iş yapacağına göre değil, TOKEN'I KİMİN TAŞIDIĞINA göre seçilir.
Ne işe yarar / ne zaman kullanılır#
GNL'in REST API'si ve Studio'su varsayılan olarak açıktır — auth yapılandırılmadıysa herkes okuyup yazabilir. roleAuth bunu tek satırda kapatır. Dört sınıfın sebebi şu: tek bir “admin” birbiriyle ilgisiz iki işi birden yapıyordu — sadece ajan koşturması gereken bir uygulama, koşuları iptal eden, bütçe düzenleyen ve org'un tüm geçmişini okuyan token'ı taşımak zorundaydı.
Bu katman açık çekirdek ve çizgi bilinçli: izolasyon ücretsiz — organizasyon sınırı, dört sınıf ve bir son kullanıcının sohbetlerini diğerinden ayıran resourceId kuralları burada, hiçbiri ödeme yapınca açılmıyor. Paralı olan kimlik yönetimi: SSO, kullanıcı başına hesap, denetim kaydı ve dört sabit sınıf yerine kişi başına izin. @gnldev/auth-ee aynı AuthProvider sözleşmesini uygular, yani yukarı geçmek çağrı yerlerinizi değiştirmez.
Kurulum / import#
import { roleAuth, type Cred } from '@gnldev/auth';
import { createRestApi } from '@gnldev/server';
import { createStudioApp } from '@gnldev/studio';@gnldev/auth tek başına bağımsız bir pakettir; @gnldev/server (REST API) ve @gnldev/studio her ikisi de auth seçeneğini AuthProvider | undefined olarak kabul eder — aynı provider hem REST hem Studio'ya aynı anda verilir.
Adım adım kullanım#
1. Her sınıf için ortamdan bir Cred üretin. Sınıflar aynı şeyin kademeleri değil — client BACKEND'inizin taşıdığı, admin bir KİŞİNİN taşıdığı şeydir:
const cred = (name: string): Cred | undefined =>
process.env.GNL_NO_AUTH === '1'
? undefined
: { token: process.env[`GNL_${name}_TOKEN`], orgId: 'acme' };
// The credential your APPLICATION carries — runs agents, manages nothing.
const clientAuth = roleAuth({ client: cred('CLIENT') });
// The credential a PERSON carries, for Studio.
const operatorAuth = roleAuth({ admin: cred('ADMIN'), viewer: cred('VIEWER') });2. Her host'a kendisine ait kimliği verin. Eskiden ikisine de tek bir auth nesnesi geçiriliyordu; artık geçirilmemeli, çünkü Studio bir operatör konsoludur ve uygulama kimliğini son kullanıcı bazında hizmete almak yerine tümden reddeder (403):
// Your backend calls this one.
app.route('/api', createRestApi(config, { title: 'My API', auth: clientAuth }));
// A person opens this one. Studio refuses an application credential outright.
app.route('/studio', createStudioApp({
reader: toJournal(storage.runs),
apiBase: '/studio',
gnl: createStudioRunner(gnl, config, { toJsonSchema: aiToolSchema }),
auth: operatorAuth,
}));3. Backend'iniz REST API'yi client token'ıyla çağırır ve her isteğin kimin adına yapıldığını söyler. Bir client kimliği tek token'la birçok kullanıcıya hizmet ettiği için resourceId zorunludur — verilmezse istek kapsamsız hizmet almak yerine reddedilir:
# resourceId names the end user this request acts for. A client credential
# serves many of them, so it is required — without it the call is a 400.
curl -s -X POST http://localhost:3001/api/agents/support/run \
-H 'content-type: application/json' \
-H "authorization: Bearer $GNL_CLIENT_TOKEN" \
-d '{"runId":"r-882","prompt":"where is my order?","threadId":"t-ayse-1","resourceId":"u-ayse"}'
# Ask for one end user's data, and have ownership checked:
curl -s "http://localhost:3001/api/runs?resourceId=u-ayse" -H "authorization: Bearer $GNL_CLIENT_TOKEN"Studio'nun SSE bağlantıları Authorization başlığı gönderemez, o yüzden aynı bearer token ?token= ile de kabul edilir (yalnız bearer, yalnız GET). Bu değer proxy loglarına ve tarayıcı geçmişine düşebilir — mümkün olduğunda Studio'nun kısa ömürlü ?ticket= akışını tercih edin.
Kapsam, rol ve aralarındaki tavan#
Buradaki yetkilendirme, bilerek ayrı tutulan iki eksendir. Kapsam, bir kimliğin nerede iş görebileceğidir — tek bir organizasyon ya da bütün platform. Rol ise ne yapabileceğidir — okuma, koşturma, yönetme. İkisini birbirine karıştırmak, yalnız tek bir kiracıyı yönetmesi gereken bir kimliğin hepsini yönetmesiyle biten yoldur.
Platform kapsamı açık bir granttır: ayrılmış platform-admin rolü. Bir kimliğin orgId taşımıyor olmasından asla türetilmez. O çıkarım klasik fail-open ayak kapanıdır — organizasyon vermeyi unutmak bir süper-admin doğururdu — bu yüzden katı model, grant'ı olmayan bağsız kimliği { kind: 'none' } sayar ve reddeder.
import { principalScope, isPlatformAdmin } from '@gnldev/auth';
principalScope({ id: 'u1', roles: ['platform-admin'] }); // { kind: 'platform' }
principalScope({ id: 'u2', roles: ['admin'], orgId: 'acme' }); // { kind: 'org', orgId: 'acme' }
principalScope({ id: 'u3', roles: ['admin'] }); // { kind: 'none' } -> strict model denies
isPlatformAdmin({ id: 'u3', roles: ['admin'] }); // false — no orgId is NOT a platform grantÖncelik bilinçlidir: açık platform grant'ı org bağını yener, çünkü platform-admin zaten organizasyonlar-arası olmak içindir. orgId taşıyan geri kalan her şey org kapsamlıdır, onun da dışında kalan hiçbir şeydir.
Tavan. Yalnız hedefin organizasyonunu doğrulayan bir kullanıcı-yönetimi yüzeyi bir delik bırakır: organizasyona bağlı bir yönetici kendisine platform-admin rolünü verip kendi organizasyonunun dışına çıkabilir. assertAssignablePrivileges bunu kapatır — hiç kimse kendisinde olmayan bir ayrıcalığı veremez.
import { assertAssignablePrivileges } from '@gnldev/auth';
const acmeAdmin = { id: 'u2', roles: ['admin'], orgId: 'acme' };
assertAssignablePrivileges(acmeAdmin, { roles: ['member'] });
// { ok: true } — an ordinary org role, the target keeps orgId 'acme'
assertAssignablePrivileges(acmeAdmin, { roles: ['platform-admin'] });
// { ok: false, reason: "only a platform-admin can grant the 'platform-admin' role" }
assertAssignablePrivileges(acmeAdmin, { permissions: ['*'] });
// { ok: false, reason: "only a platform-admin can grant the '*' (all-permissions) grant" }Kural bilerek asgaridir. Bir platform-admin her şeyi atayabilir. Başka herkes ne platform-admin rolünü ne de '*' tüm-izinler grant'ını verebilir — ikincisi aynı yükseltmenin rol ekseni yerine izin ekseninde ifade edilmiş hâlidir. Sıradan organizasyon rolleri atanabilir kalır, çünkü hedef kendi orgId'sini korur ve hiçbir şey organizasyon sınırını geçmez.
API referansı#
roleAuth{ superAdmin?, admin?, client?, viewer? } → AuthProvider | undefined. Hiçbir sınıf verilmezse undefined döner (opt-in). Yalnızca { admin, viewer } verildiğinde, iki yeni sınıf hiç yokmuş gibi davranır.
principalScopeKapsamı türetir: { kind: 'platform' } | { kind: 'org', orgId } | { kind: 'none' }. Açık platform grant'ı org bağını yener.
isPlatformAdminYalnız principal açık platform-admin rolünü taşıyorsa true. Bağsız olmak (orgId yokluğu) bilerek yeterli değildir.
assertAssignablePrivilegesKullanıcı oluşturma/güncellemede ayrıcalık tavanı: platform-admin olmayan biri platform-admin rolünü ya da '*' iznini vermeye çalışırsa { ok: false, reason } döner.
PrincipalScopeBir kimliğin çözülmüş 'nerede'si — rolünden bağımsız.
AssignabilityResult{ ok: true } | { ok: false, reason } — tavan kontrolünün sonucu.
CLIENT_WRITESBir client kimliğinin yapabileceği yazma işlemlerinin tam kümesi (agents:run, workflow:run, run:cancel). BEYAZ LİSTEDİR — bu kümede adı geçmeyen bir yazma reddedilir, sonraki sürümlerde eklenen rotalar dahil.
PLATFORM_ADMIN_ROLEsuperAdmin'in taşıdığı, organizasyonlar-arası ayrılmış yetki. Eksik bir orgId'den ASLA türetilmez — o çıkarım, unutulan bir yapılandırma satırını organizasyonlar-arası bir süper admine çevirirdi.
makeGateAuthProvider'ı ortak bir Hono kapısına (allow/deny) çevirir — REST ve Studio bu mantığı tekrar yazmaz.
principalOfallow() sırasında doğrulanan Principal'ı context'ten okur (aynı istek içinde) — org/audit buradan türetilir.
normalizeAuthAuthProvider | ReadWriteAuth | undefined → AuthProvider | undefined. Host'lar tek tipe indirger.
fromReadWriteEski { read, write } predicate çiftini (StudioAuth) AuthProvider'a sarar — geri uyumluluk köprüsü.
AuthProviderStabil sözleşme: authenticate(c), authorize(principal, c, ctx), opsiyonel capabilities().
PrincipalKimliği doğrulanmış özne: { id?, roles, orgId?, permissions? }. Ücretsiz katman roles kullanır; 'permissions' paralı RBAC'in doldurduğu alandır.
Decision{ allow: true } | { allow: false, status?: 401 | 403, reason? } — yetki kararı.
Cred{ token?, user?, pass?, orgId?, platformAdmin? } — bir sınıfın bearer token'ı ve/veya basic kimliği. 'orgId' kimliği tek bir organizasyona bağlar; başka bir organizasyonu adlandıran istek reddedilir. 'platformAdmin: true' herhangi bir sınıfa ayrılmış platform-admin rolünü enjekte eder — 'superAdmin' sınıfının adıyla taşıdığı organizasyonlar-arası yetkinin statik-yapılandırma kapısıdır. Sınıfı tercih edin: o, kimliğin NE OLDUĞUNU söyler; diğeri sıradan bir admin gibi okunan birine yetki ekler.
ReadWriteAuth{ read?, write? } predicate çifti — fromReadWrite ile AuthProvider'a dönüştürülür.
roleAuth({}) undefined döner ve GNL açık davranışını korur — geriye dönük kırılma yok. Ama üretimde sağlayıcının olmaması sessiz bir açık kapı değil, hatadır.AuthProvider arayüzünü SSO/RBAC/çok-organizasyonluluk gerektiren dağıtımlar için paralı @gnldev/auth-ee implemente eder — geçiş REST/Studio tarafında kod değişikliği gerektirmez, yalnızca auth değerini değiştirmek yeterlidir.