@dofe/sso-node
Runtime-neutral Node SDK for sso.dofe.ai internal service APIs.
Install
pnpm add @dofe/sso-node
Usage
import { createSsoInternalClient } from '@dofe/sso-node';
const sso = createSsoInternalClient({
baseUrl: process.env.SSO_INTERNAL_API_URL!,
internalSecret: process.env.INTERNAL_API_SECRET!,
serviceName: 'agents.dofe.ai',
});
const user = await sso.users.get('00000000-0000-0000-0000-000000000000');
const permissions = await sso.permissions.getUserPermissions(user.id);
For migration from older local clients that expose flat methods, use createSsoLegacyClient:
import { createSsoLegacyClient } from '@dofe/sso-node/legacy';
const sso = createSsoLegacyClient({
baseUrl: process.env.SSO_INTERNAL_API_URL!,
internalSecret: process.env.INTERNAL_API_SECRET!,
internalBasePath: '/api/internal',
authMode: 'x-internal-secret',
serviceName: 'agents.dofe.ai',
});
const user = await sso.getUser('00000000-0000-0000-0000-000000000000');
const hasPermission = await sso.checkPermission(user.id, 'team.read', 'team-id');
const role = await sso.getUserTeamRole('team-id', user.id);
Auth Modes
| Mode | Header |
|---|---|
bearer | Authorization: Bearer <internalSecret> |
x-internal-secret | X-Internal-Secret: <internalSecret> |
The default mode is bearer. Use x-internal-secret only for legacy services while they migrate.
The default fetcher rejects HTTP redirects for both typed methods and raw requests,
including redirects to the same origin. Set baseUrl and internalBasePath to the
final internal API address. This prevents forwarding service proofs or legacy secret
headers to a redirect destination. A custom fetcher must enforce the same rule.
Validation
Run pnpm --filter @dofe/sso-node test to build and test the package. Redirect tests
use local HTTP servers and synthetic credentials; they require no live SSO access.
Boundary
This package is a client-only SDK. It does not include NestJS modules, Redis subscribers, Prisma, migrations, seeds, or database connection logic.
Optional session timing
SessionV2ClientOptions.onTiming and SessionV2BffOptions.onTiming accept
SessionV2TimingObserver, (event: SessionV2TimingEvent) => void | Promise<void>.
All timing types are exported from the package root. The hook is optional;
synchronous throws and asynchronous rejections are ignored, and promises are never
awaited. Keep observer synchronous work short.
Events contain only phase, operation, nonnegative durationMs, outcome
(success or error), and optional numeric HTTP status. No identifiers,
request objects, URLs, credentials, or response payloads are emitted.
| Phase | Operation | Boundary |
|---|---|---|
remote | exchange-code, rotate, introspect, renew-ticket, confirmation, bootstrap-exchange, logout-confirmation, logout, call | SSO fetch through JSON body parsing and HTTP error handling. call is the fixed fallback for other paths. Method-level schema checks after the call are outside this span. |
active-entry | read | Vault read, expiry and revocation checks. |
admission | begin-dispatch, begin-window-dispatch | Vault admission, including its remote introspection and lease acquisition. |
completion | complete | Vault completion commit. |
compat-dispatch | dispatch | Compatibility upstream fetch through buffered body and completion, or stream handoff; excludes subsequent streaming duration. |
Nested spans overlap: do not sum admission/completion and their remote spans.
status is the original HTTP response status, even when the SDK normalizes an
error to 503. A remote success describes HTTP/JSON completion, not an active
session grant. Compatibility HTTP statuses >= 400 produce error outcomes without
changing the upstream response. Direct transport remains in-process and emits no
compat-dispatch span. Compatibility response forwarding preserves Server-Timing
and the existing x-request-id; it does not inject or rewrite proof-bound request
headers or forward arbitrary upstream headers. No correlation identifier is added
to events; applications may associate events using their own async context.