@tealbrick/portal
v0.2.7
Published
Independent Tealbrick portal authentication and HTTP client. ESM, Node 24+. Core depends only on `jose`; the explicit `/eve` adapter supports Eve 0.55.x as an optional peer. No extension mounting, Knowledge, Buzz, tools, channels, skills or connections ar
Readme
@tealbrick/portal
Independent Tealbrick portal authentication and HTTP client. ESM, Node 24+. Core depends only on jose; the explicit /eve adapter supports Eve 0.55.x as an optional peer. No extension mounting, Knowledge, Buzz, tools, channels, skills or connections are contributed.
import { portalAuth } from '@tealbrick/portal/eve';
const auth = portalAuth({ issuer: 'https://portal.example', org: 'org-id', agent: 'helper' });Both organization and registered agent name are required trusted deployment configuration. The adapter uses Eve's actual ForbiddenError and UnauthenticatedError classes. Authentication failure returns null; authorization denial throws ForbiddenError; key retrieval outage throws UnauthenticatedError with identity_verification_unavailable. Eve's error class still represents an authentication HTTP response; core reports outages as status 503 for custom HTTP handlers. No retry or implicit offline access.
Core createPortalVerifier(options).authenticate(request) returns an AuthResult. ES256 identity and policy signatures, issuer, mandatory expiry/issued-at, user/org matching and use/manage grants are checked. Identity kind/purpose separation excludes refresh, daemon and credential JWTs. A2A requires kind, caller, callee and audience eve:ORG/AGENT. Native bundle header is X-Tealbrick-Bundle. Legacy X-Kybernesis-Bundle is enabled explicitly by the default compatibility option; set legacyBundleHeader:false to disable it. Conflicting dual headers are rejected. Existing public identity sessions without a typ remain supported; typ:user is accepted.
Future attachment gate
verifier.verifyAttachment(token, principal, {agentId,audience,capability}) accepts only the exact unexpired principal returned by that verifier instance. An object assembled from labels or JSON is rejected. The required logical agent ID, audience and capability are trusted runtime configuration, never browser request inputs. For human requests the preceding grant authenticates access to this configured deployment; agent callers must also match the required agent ID.
Portal must sign typ:attachment, orgId, sub:logicalAgentId, aud, capabilities:string[], status:active, jti, iat, exp with ES256. Maximum attachment lifetime is 300 seconds. Verification occurs for each protected operation, rejects inactive/expired/unrelated grants and never consumes unsigned manifests. Refresh must obtain a newly issued attachment: a revoked attachment can remain usable until its signed expiry, at most 300 seconds. Immediate revocation requires an online introspection mechanism, which this package does not provide. This verifies evidence only; it mounts no miniapp.
HTTP client
new PortalClient({issuer,token,fetch?,timeoutMs?}) provides startDevice, pollDevice, refresh, registerAgent, mintAgentCredential, startEnrollment, pollEnrollment, peers, mintAgentSession and resolveSpeaker. Generic request<T> is restricted to same-origin /api/ paths. Redirects fail closed and requests time out after 15 seconds. No automatic retry of writes, credential persistence or logging. Keep credentials in trusted runtimes. PortalHttpError.status/code distinguishes pending/denied/server failures; response content and credential strings are excluded from its message.
resolveSpeaker makes a separate portal request for every provider/externalId pair and turn, with no room-level or durable cache. It returns a discriminated linked/unlinked/refused result for unlinked, suspended, unknown or ungranted senders; transport/server failures throw. Endpoint response shapes are checked at runtime; generic request performs only envelope validation for unknown routes. The response is typed but is not authentication evidence until independently verified by the receiving agent. Enrollment approval and denial are operator-side portal actions, not automated client permissions.
Evidence and limits
npm run check --workspace @tealbrick/portal builds declarations and runs disposable ES256 fixture tests: valid identity, mismatched user/org, same-name foreign org, missing/malformed grants, credential type confusion, invalid/expired signatures, A2A audience, key outage, actual Eve errors, attachment bindings/status, and transport security. No reference service mutations or AVM execution. Live portal lifecycle and desktop UAT, real JWKS outage/rotation, and all portal routes are separate integration evidence; this package does not claim complete enterprise parity. Refresh/enrollment client response structures preserve current wire compatibility and do not independently prove server authorization.
Eve 0.55 channel and model example
Keep channel authorization in agent/channels/eve.ts:
import { eveChannel } from 'eve/channels/eve';
import { portalAuth } from '@tealbrick/portal/eve';
export default eveChannel({
auth: [portalAuth({
issuer: process.env.TEALBRICK_ISSUER!,
org: process.env.TEALBRICK_ORG_ID!,
agent: 'helper',
})],
});Configure the model independently in agent/agent.ts using the runtime's own provider setup:
import { defineAgent } from 'eve';
import { chatgpt } from 'eve/models/openai';
export default defineAgent({
model: chatgpt(process.env.TEALBRICK_MODEL),
reasoning: 'low',
});The portal token authenticates incoming requests. It is not a model-provider credential; model login and subscription configuration remain local to the agent runtime. TEALBRICK_ORG_ID is the actual portal organization ID, not its display name. Missing org/agent configuration fails during verifier creation.
