npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@agdsoftware/sso-sdk

v2.7.2

Published

Keycloak/OIDC tabanlı merkezi yetkilendirme SDK'sı — evrensel izin istemcisi, sunucuda JWT doğrulama, web ve mobil için PKCE, NestJS guard zinciri ve React hook'ları; tek paket, ayrı girişler.

Downloads

1,144

Readme

@agdsoftware/sso-sdk

Kimliği Keycloak/OIDC'den, yetkiyi merkezi bir IAM servisinden alan uygulamalar için tek SDK — Node, tarayıcı, React Native.

Tek cümle: token "bu kişi kim" der, IAM "ne yapabilir" der. İkisi ayrıdır ve ayrı kalmalıdır.

Girişler

Paket tektir, girişleri ayrıdır. Her alt yol yalnız kendi ortamının ağırlığını taşır: bir React uygulaması @nestjs/* çözümlemez, bir NestJS API'si oidc-client-ts indirmez, mobil hiçbirini görmez.

| Giriş | Ortam | İçerik | |---|---|---| | @agdsoftware/sso-sdk | evrensel | tipler, hatalar, IamClient, AuthzStore, SsoSession, kapsam yardımcıları | | @agdsoftware/sso-sdk/server | yalnız sunucu | createTokenVerifier (jose + JWKS) | | @agdsoftware/sso-sdk/oidc | tarayıcı + mobil | PKCE, keşif, çıkış adresi — kütüphanesiz | | @agdsoftware/sso-sdk/nestjs | NestJS API'ler | guard zinciri, dekoratörler, /sso/me, sağlık ucu | | @agdsoftware/sso-sdk/nestjs/typeorm | NestJS + TypeORM | satır kapsamı için query-builder yardımcısı | | @agdsoftware/sso-sdk/nestjs/testing | testler | sahte SsoContext | | @agdsoftware/sso-sdk/react | React arayüzler | SsoProvider, izin hook'ları, HTTP köprüleri |

Kök giriş fetch dışında hiçbir şeye bağlı değildir. Bölünmenin sebebi tek cümle: token'ı mobil taşır, sunucu doğrular.

Kurulum

npm i @agdsoftware/sso-sdk

Çerçeve bağımlılıkları opsiyonel peer'dır — kullandığınız girişin istediğini siz kurarsınız, ötekiler hiç inmez:

# NestJS API
npm i @agdsoftware/sso-sdk @nestjs/common @nestjs/core reflect-metadata rxjs
npm i typeorm                       # yalnız /nestjs/typeorm kullanacaksanız

# React web
npm i @agdsoftware/sso-sdk react oidc-client-ts

# Mobil / saf Node — hiçbir ek paket gerekmez
npm i @agdsoftware/sso-sdk

Registry'siz ortamda: pnpm sdk:pack → npm i ./vendor/agdsoftware-sso-sdk-*.tgz


Sunucu (Express, Fastify, script)

import { IamClient, bearerToken } from '@agdsoftware/sso-sdk';
// Token doğrulama SUNUCUYA özgüdür (jose): kök giriş evrensel kalsın,
// mobil/tarayıcı onu bundle'a almasın diye `/server` altındadır.
import { createTokenVerifier } from '@agdsoftware/sso-sdk/server';

const verify = createTokenVerifier({
  issuer: process.env.SSO_ISSUER!,      // http://localhost:8080/realms/agd
  audience: process.env.SSO_AUDIENCE!,  // SSO ekibinin verdiği audience
});

const iam = new IamClient({ baseUrl: process.env.IAM_URL! });

// istek başına
const token = bearerToken(req.headers.authorization);
const claims = await verify(token!);                     // 401 riski burada
await iam.assert(token!, groupId, 'ornek:kayit:onayla'); // 403 riski burada

groupId kullanıcının aktif birimidir; iam.me(token) ile listelenir ve kullanıcıya seçtirilir (birden fazla birimde görevli olabilir).

import { bearerToken, IamClient, ForbiddenError, SsoTokenError } from '@agdsoftware/sso-sdk';
import { createTokenVerifier } from '@agdsoftware/sso-sdk/server';

const authenticate = async (req, res, next) => {
  try {
    const token = bearerToken(req.headers.authorization);
    if (!token) throw new SsoTokenError('Token yok');
    req.token = token;
    req.user = await verify(token);
    next();
  } catch (error) {
    next(error);
  }
};

const requirePermission = (permission: string) => async (req, res, next) => {
  try {
    // Bağlam birimi istemciden gelir ama YETKİ IAM'de doğrulanır:
    // uydurulmuş bir groupId yalnız "yetkin yok" cevabı üretir.
    await iam.assert(req.token, req.header('X-Organization-Group-Id'), permission);
    next();
  } catch (error) {
    next(error);
  }
};

app.get('/kayitlar', authenticate, requirePermission('ornek:kayit:goruntule'), handler);

app.use((error, req, res, next) => {
  if (error instanceof SsoTokenError) return res.status(401).json({ code: 'UNAUTHENTICATED' });
  if (error instanceof ForbiddenError) return res.status(403).json({ code: 'FORBIDDEN' });
  next(error);
});

NestJS

Kendi guard'ınızı yazmayın; zincir hazır: kimlik → bağlam → karar.

1. Modül

import { SsoModule } from '@agdsoftware/sso-sdk/nestjs';

@Module({
  imports: [
    // SSO_ISSUER · SSO_AUDIENCE · IAM_BASE_URL okunur ve DOĞRULANIR.
    // Eksik varsa süreç AÇILIŞTA durur — eksik yapılandırmayla ayağa kalkıp
    // ilk isteğe kadar sağlıklı görünmek, sorunu üretimde bulmaktır.
    SsoModule.forRootFromEnv({
      publicMetadataKeys: ['isPublic'],  // var olan @Public() dekoratörünüz
    }),
  ],
})
export class AppModule {}

forRoot({ issuer, audience, iamBaseUrl, … }) ve forRootAsync({ useFactory }) de durur.

2. Controller

@Controller('toplantilar')
export class MeetingsController {
  @Get()
  @RequirePermission('mgkys:toplanti:goruntule')
  list(@CurrentContext() ctx: SsoContext) { ... }

  @Get('profilim')
  @SelfScoped()                       // izin yok, kimlik yeter
  me(@CurrentSsoUser() user: SsoIdentity) { ... }
}

Dekoratör taşımayan handler 403 alır (fail-closed). Unutulan uç açık kalmaz.

3. Satır kapsamı

Her kaydınıza sahibi birimin Keycloak yolunu yazın (GroupPath = /İzmir/Konak):

import { applyGroupScope } from '@agdsoftware/sso-sdk/nestjs/typeorm';
import { ownerGroupPath, assertInScope } from '@agdsoftware/sso-sdk/nestjs';

applyGroupScope(qb, ctx);                 // listeleme: /İzmir → /İzmir ve altı
meeting.GroupPath = ownerGroupPath(ctx);  // oluşturma: istemciden gelen birim kabul edilmez
assertInScope(ctx, meeting.GroupPath);    // güncelleme/silme: okuma filtresi tek başına yetmez

Dekoratörler

| Dekoratör | Anlamı | |---|---| | @RequirePermission(code) | Bu izin yoksa 403 | | @RequireAnyPermission(a, b) | Herhangi biri yeter (VEYA) | | @PermissionResource('mgkys:toplanti') | Sınıf seviyesi; devralınan add/get/getAll/update/delete için izni metot adından türetir | | @SelfScoped() | Kimlik yeter; handler yalnız @CurrentSsoUser() kimliğini kullanmalı | | @AnyAuthenticated() | Kimlik yeter; yanıt kişiye göre değişmez (referans veriler) | | @SsoPublic() | Token bile gerekmez | | @CurrentContext() / @CurrentSsoUser() / @CurrentGroup() | Parametre dekoratörleri |

@RequirePermission sınıf seviyesinde yasaktır (TypeError): sınıfa yazılan izin sonradan eklenen her handler'a sessizce miras kalır.

Genişletme noktaları

@Global()
@Module({
  providers: [
    { provide: SSO_USER_RESOLVER, useClass: UsersLinkService },  // sub → Users.Id
    { provide: SSO_CACHE, useClass: RedisSsoCache },             // yoksa süreç içi bellek
  ],
  exports: [SSO_USER_RESOLVER, SSO_CACHE],
})
export class SsoBridgeModule {}
  • SsoUserResolver — SSO kullanıcısını kendi tablonuza bağlar. null dönerse istek 403 SSO_USER_NOT_LINKED alır (sessizce yeni kullanıcı sayılmaz).
  • SsoCacheAdapter — çok instance'lı kurulumda Redis verin.

Katalog doğrulaması

// permissions/catalog.ts (dosyanın sonunda)
registerPermissionCatalog((code) => CODE_SET.has(code));

Katalogda olmayan bir kod açılışta TypeError verir. Yakalanmazsa o uç sessizce herkese kapanır ve 403'ün nedeni aylarca aranır.

Testler

Guard'ları mock'lamayın — mock'lanmış guard her zaman "geçti" der ve testler yetki hatalarını göremez. Bağlamı kurgulayın:

import { createTestSsoContext } from '@agdsoftware/sso-sdk/nestjs/testing';

const ctx = createTestSsoContext({
  permissions: ['mgkys:toplanti:goruntule'],
  group: { path: '/İzmir/Konak', unitType: 'ILCE' },
});

React

Arayüzde izin kontrolü görgü kuralıdır, güvenlik sınırı değil: gizlenen buton isteği elle atmayı engellemez. Gerçek kontrol her zaman API'dedir.

import { SsoProvider, RequireSso, ssoConfigFromEnv } from '@agdsoftware/sso-sdk/react';

// VITE_SSO_ISSUER · VITE_SSO_CLIENT_ID · VITE_SSO_REDIRECT_URI · VITE_API_BASE_URL
// (Next.js: ssoConfigFromEnv(process.env, 'NEXT_PUBLIC_'))
<SsoProvider config={ssoConfigFromEnv(import.meta.env)} fallback={<Splash />}>
  <RequireSso loading={<Splash />}>
    <App />
  </RequireSso>
</SsoProvider>

redirectUri tam adres olmalı — joker (http://localhost:5173/*) SSO tarafında IAM_CLIENT_WILDCARD_FORBIDDEN ile reddedilir (açık yönlendirme riski).

İzinler

const can = usePermission('mgkys:toplanti:guncelle');

if (can === 'loading') return <Skeleton />;   // ÖNCE bunu yazın
return can ? <EditButton /> : null;

'loading' truthy bir string'tir: {can && <EditButton/>} yazmak butonu yükleme sırasında gösterir. Üç durum bilerek var — iki duruma indirmek ilk boyamada menüyü zıplatır.

<Can permission="mgkys:toplanti:olustur" loading={<Skeleton/>}>
  <NewMeetingButton />
</Can>

// kaynak-kısıtlı yetki
const canEdit = useResourcePermission('mgkys:toplanti:guncelle', 'toplanti', meeting.id);

Aktif birim

Kullanıcı birden fazla birimde görevli olabilir; izinler birime bağlıdır.

const { groups, activeGroup, setActiveGroup } = useGroups();

<select value={activeGroup?.id} onChange={(e) => setActiveGroup(e.target.value)}>
  {groups.map((g) => <option key={g.id} value={g.id}>{g.path}</option>)}
</select>

Birim değişince izinler otomatik yeniden çekilir.

Aktif birimin tipli künyesi

Yol (/İzmir/Konak/Medya Komisyonu) hangi halkanın ne olduğunu söylemez; sunucu her grubu tipli döker, hook hazır okur:

const units = useActiveUnits();
// { IL: 'İzmir', ILCE: 'Konak', KOMISYON: 'Medya Komisyonu' } | null

<p>{units?.IL} · {units?.ILCE ?? 'tüm il'}</p>

Çekirdek yardımcıları her ortamda çalışır: unitOf(grup, 'MAHALLE'), unitsOf(grup), describeUnits(grup), ORG_UNIT_LABELS.

İstekler ve çıkış

const ssoFetch = useSsoFetch();
await ssoFetch('/api/toplantilar');

const { getAuthHeaders, logout } = useSso();
useEffect(() => attachSsoToAxios(api, getAuthHeaders), [getAuthHeaders]);

İkisi de Authorization: Bearer … ve X-Organization-Group-Id ekler. logout OIDC end-session ucuna id_token_hint ile gider: Keycloak oturumu da kapanır. Yalnız yerel state'i temizlemek, kullanıcının bir sonraki girişte şifresiz geri dönmesi demektir — SSO'da "çıktım" yanılsamasının klasik kaynağı.

| Hook / Bileşen | İş | |---|---| | useSso() | Durum, kullanıcı, birimler, izinler, login/logout/refresh | | usePermission(code) | 'loading' \| true \| false | | useAnyPermission(...codes) | Herhangi biri | | useResourcePermission(code, type, id) | Kayıt bazlı yetki | | useGroups() | Birim listesi + aktif birim + değiştirme | | useActiveUnits() | Aktif birimin tipli künyesi ({IL, ILCE, MAHALLE, …}) | | useSsoFetch() | Başlıkları ekleyen fetch | | attachSsoToAxios(instance, getAuthHeaders) | axios interceptor (axios'a bağımlılık yok) | | <RequireSso> | Oturum kurulana kadar bekletir, yoksa girişe yollar | | <Can permission> | İzin varsa çocukları gösterir |


Mobil (React Native / Expo)

Mobilde iframe yoktur, yönlendirme özel şemayla döner ve tarayıcı OIDC kütüphaneleri ya çalışmaz ya ağır gelir. /oidc girişi bunun için var — kütüphanesiz, tek ihtiyacı sistem tarayıcısını açacak bir fonksiyon:

import { discover, createPkce, buildAuthorizeUrl, parseCallback, exchangeCode }
  from '@agdsoftware/sso-sdk/oidc';

const endpoints = await discover(ISSUER);
const pkce = await createPkce();
const url = buildAuthorizeUrl({ endpoints, clientId, redirectUri, pkce });
const returned = await WebBrowser.openAuthSessionAsync(url, redirectUri); // Expo
const tokens = await exchangeCode({
  endpoints, clientId, redirectUri, code: parseCallback(returned.url).code!, pkce,
});

Parola akışı (direct access grant) kullanılmaz: uygulamanın kullanıcı parolasını görmesi, MFA'yı ve giriş ekranındaki bot korumasını birlikte devre dışı bırakır. Ortam gereksinimi: crypto.getRandomValues + crypto.subtle (react-native-get-random-values ve expo-crypto).

Ayrıntı: docs/MOBIL.md


Ortak kavramlar

Birden fazla izin bakacaksanız

Tek tek check yerine bir kez effectivePermissions alın — tek gidiş-dönüş:

const effective = await iam.effectivePermissions(token, groupId);
const canApprove = effective.permissions.includes('ornek:kayit:onayla');

İstemci 30 sn yerel cache tutar (cacheTtlMs ile değiştirilir, 0 kapatır).

İl / ilçe filtrelemesi

iam.me(token) her birim için türü ve türetilmiş il/ilçe yolunu verir:

{ "path": "/İzmir/Konak/Eğitim", "unitType": "KOMISYON",
  "il": "/İzmir", "ilce": "/İzmir/Konak" }

ilce boşsa kullanıcı il seviyesindedir — "tüm il" demektir.

Kaynak (kayıt) bazlı yetki

Bir yetki tek tek kayıtlara kısıtlanabilir. Kısıtlı izin genel sete girmez:

const allowed = canAccessResource({
  grants: effective.resourceGrants,
  permission: 'ornek:kayit:guncelle',
  resourceType: 'kayit',
  resourceId: record.id,
  recordGroupPath: record.groupPath,   // ZORUNLU
  contextGroupPath: activeGroup.path,
});

recordGroupPath kontrolü zorunludur: kaynaklar IAM için opaktır, kaydın gerçekten o birime ait olduğunu IAM doğrulayamaz.

Hata sözleşmesi

| Durum | Kod | Anlamı | |---|---|---| | 401 | SSO_TOKEN_MISSING / SSO_TOKEN_INVALID | Kimlik yok/geçersiz | | 403 | SSO_USER_NOT_LINKED | Kimlik var, uygulamada karşılığı yok | | 403 | SSO_GROUP_NOT_MEMBER | İstenen birimin üyesi değil | | 403 | IAM_PERMISSION_DENIED | Yetki yok (hangi izin olduğu söylenmez) | | 403 | IAM_SCOPE_DENIED / IAM_RESOURCE_DENIED | Kayıt yetki alanı dışında | | 503 | IAM_UNAVAILABLE | IAM'e ulaşılamadı — yetkisizlik değildir |

503'ü 403'e katlamayın: bir kesintiyi yetki hatası gibi göstermek ekibi günlerce yanlış yerde arattırır.

Sık yapılan hatalar

| Hata | Belirti | Çözüm | |---|---|---| | issuer tam eşleşmiyor | unexpected "iss" claim value | Token'daki iss ne ise birebir onu verin — localhost yerine 127.0.0.1 bile yeter | | audience doğrulanmıyor | Sessiz güvenlik açığı | Başka uygulamanın token'ı kabul edilir; SSO ekibinden audience isteyin | | Yetkiyi token'dan okumak | Süresi dolan yetki 5 dk yaşar | Karar daima iam.check() / effectivePermissions() | | Yerel izin cache'i 60 sn'yi aşıyor | Geri alınan yetki o kadar yaşar | TTL'i kısa tutun | | X-Organization-Group-Id'ye güvenmek | Yetki yükseltme | Paket her istekte kullanıcının IAM'deki birim listesiyle doğrular |

1.x'ten geçiş

SDK 2.0.0'da üç paket tek pakette birleşti. Kod değişmedi; yalnız import yolları değişti:

| 1.x | 2.x | |---|---| | @agdsoftware/sso-client | @agdsoftware/sso-sdk | | @agdsoftware/sso-client/server | @agdsoftware/sso-sdk/server | | @agdsoftware/sso-client/oidc | @agdsoftware/sso-sdk/oidc | | @agdsoftware/nestjs-sso | @agdsoftware/sso-sdk/nestjs | | @agdsoftware/nestjs-sso/typeorm | @agdsoftware/sso-sdk/nestjs/typeorm | | @agdsoftware/nestjs-sso/testing | @agdsoftware/sso-sdk/nestjs/testing | | @agdsoftware/react-sso | @agdsoftware/sso-sdk/react |

Tek davranış farkı: oidc-client-ts artık opsiyonel peer'dır. React web kullanıyorsanız onu kendiniz kurarsınız; NestJS ve mobil tarafında artık hiç inmez.

Arka plan yetki yenilemesi — 2.7.0

React refreshIntervalMs zamanlayıcısı aynı hazır bağlamı arka planda yeniler; başarılı aynı bağlam yanıtı form ağacını yükleme ekranıyla değiştirmez. Çekirdekte açık kullanım store.refresh({ background: true }) biçimindedir. Parametresiz refresh() ve birim değişimi eski yükleme davranışını korur. Token kaybı veya son hata, eski izinleri, grant'leri ve aktif kapsamı temizler.

Tüketici; aktör, yetki sürümü, izinler veya kayıt kapsamı değiştiğinde kendi sorgu cache'ini ve korumalı ekran durumunu geçersizleştirmelidir. Aynı hazır bağlamın bekleme sırasında korunması API yetkilendirmesinin yerine geçmez. Yoklama aralığı, sunucu cache süresi ve ağ gecikmesi birlikte değerlendirilir; anlık iptal veya belli bir kapasite garantisi verilmez.

İlgili

2.7.1 düzeltmesi: yetki yenilemesinin token alma, HTTP/gövde okuma ve tekrarlar dahil toplam süresi 10 saniyeyle sınırlıdır. Süre dolunca eski yetki temizlenir; abort'u dikkate almayan adaptörün geç yanıtı bağlamı geri getiremez.