@cotal-ai/auth 0.157.9 → 0.158.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/service.js CHANGED
@@ -90,6 +90,8 @@ import { authorizeRemoteManagerGoalIndexScan, completeRemoteManagerGoalIndexScan
90
90
  import { authorizeRemoteManagerAdmin } from "./manager-admin-authorization.js";
91
91
  import { authorizeRemoteManagerParticipation } from "./manager-participation-authorization.js";
92
92
  import { authorizeRemoteManagerMaintenance, completeRemoteManagerMaintenance } from "./manager-maintenance.js";
93
+ import { deregisterUserEndpoint } from "./endpoint-deregister.js";
94
+ import { parseRemoteEndpointDeregisterRequest } from "@cotal-ai/core";
93
95
  import { validateRetainedManagedAgent } from "./continuity.js";
94
96
  import { reconstructRemoteManagerServeGrant, remoteManagerServeGrantFromCluster, remoteManagerSurface } from "./manager-contract.js";
95
97
  import { authorityBarrierGrants, authorityWriterGrants, openAuthorityClient, openSupervisedConnectReader, platformReadinessGrants, remoteManagerIssuerGrants, remoteManagerRegistrationProof, registrationExecutorGrants, servedRunRequestSubjects, servedRunCancelRequestSubject, servedSpawnRequestSubjects, serviceCalloutWriterGrants, serviceHostDiscoveryGrants } from "./authority-client.js";
@@ -1361,6 +1363,10 @@ async function buildAuthAuthorityPlane(opts, unwind, onConnection) {
1361
1363
  }
1362
1364
  throw new EpEnvelopeError("conflict", `ending the platform supervisor of ${owner} lost repeated CAS attempts on ${key}; retry`);
1363
1365
  },
1366
+ deregisterEndpoint: async (request, granter) => {
1367
+ refuseIfFenced();
1368
+ return deregisterUserEndpoint({ request, granter, space, server, auth: issuerAuth(), observeGate: observeManagerGate, onConnection });
1369
+ },
1364
1370
  maintainRemoteManager: async (holder) => {
1365
1371
  refuseIfFenced();
1366
1372
  const { owner } = holder;
@@ -2624,6 +2630,7 @@ async function startAuthContext(o) {
2624
2630
  ownerSecret,
2625
2631
  authorizeActor,
2626
2632
  managerServiceAuthority: plane.issueManagerServiceAuthority,
2633
+ deregisterEndpoint: plane.deregisterEndpoint,
2627
2634
  maintainRemoteManager: plane.maintainRemoteManager,
2628
2635
  validateRetainedAgent: plane.validateRetainedAgent,
2629
2636
  verifyManagedAgentEnrollment: plane.verifyManagedAgentEnrollment,
@@ -2823,6 +2830,7 @@ async function startAuthContext(o) {
2823
2830
  return await invokeCommand(readerNc, space, await resolveService(readerNc, space, "manager", caller, { instanceId, commands: ["status"] }), "status", undefined, {});
2824
2831
  };
2825
2832
  const service = {
2833
+ deregisterEndpoint: (request, granter) => { refuseUnlessReady(); return plane.deregisterEndpoint(request, granter); },
2826
2834
  url,
2827
2835
  ...(publicUrl !== undefined ? { publicUrl } : {}),
2828
2836
  cap,
@@ -3105,6 +3113,7 @@ const ROUTES = new Map([
3105
3113
  ],
3106
3114
  ["/exchange", (req, res, ctx) => handleExchange(req, res, ctx, LOOPBACK_POLICY)],
3107
3115
  ["/manager-service-authority", (req, res, ctx) => handleManagerServiceAuthority(req, res, ctx, LOOPBACK_POLICY)],
3116
+ ["/endpoint-deregister", (req, res, ctx) => handleEndpointDeregister(req, res, ctx, LOOPBACK_POLICY)],
3108
3117
  ["/manager-instances", (req, res, ctx) => handleManagerInstances(req, res, ctx, LOOPBACK_POLICY)],
3109
3118
  [SERVICE_ENDPOINT_AUTHORITY_PATH, (req, res, ctx) => handleServiceEndpointAuthority(req, res, ctx, LOOPBACK_POLICY)],
3110
3119
  [SERVICE_ENDPOINT_HOST_PATH, (req, res, ctx) => handleServiceEndpointHost(req, res, ctx, LOOPBACK_POLICY)],
@@ -3872,9 +3881,50 @@ async function handleServiceEndpointAuthority(req, res, ctx, policy) {
3872
3881
  return send(res, 503, { error: "service endpoint authority did not complete; the request may be bound, so retain its request identity" }, noStore);
3873
3882
  }
3874
3883
  }
3875
- /** The typed manager authority exchange, served on both faces. The face's policy decides whether the
3876
- * loopback capability is demanded; on the public face the verified IdP JWT is the proof, and the
3877
- * dispatcher reads the caller's scope from the ledger either way. */
3884
+ /** Same-account human removal. Operator authority exists only on the trusted in-process door. */
3885
+ async function handleEndpointDeregister(req, res, ctx, policy) {
3886
+ if (req.method !== "POST")
3887
+ return send(res, 405, { error: "POST only" });
3888
+ if (req.headers.origin !== undefined)
3889
+ return send(res, 403, { error: "browser-origin requests are not served here" });
3890
+ if (!/^application\/json\b/.test(req.headers["content-type"] ?? ""))
3891
+ return send(res, 415, { error: "content-type must be application/json" });
3892
+ if (policy.requireCapability && req.headers.authorization !== `Bearer ${ctx.cap}`)
3893
+ return refuseCapability(res, ctx, "missing/invalid exchange capability");
3894
+ const peer = policy.peerKey(req), throttled = policy.throttled(ctx, peer);
3895
+ let body;
3896
+ try {
3897
+ const raw = await readJsonBody(req);
3898
+ if (!raw || typeof raw !== "object" || Array.isArray(raw) || Object.keys(raw).sort().join(",") !== "idpToken,request" ||
3899
+ !("idpToken" in raw) || typeof raw.idpToken !== "string" || !raw.idpToken)
3900
+ return send(res, 400, { error: "endpoint deregistration needs only { idpToken, request }" });
3901
+ body = raw;
3902
+ parseRemoteEndpointDeregisterRequest(body.request);
3903
+ }
3904
+ catch (error) {
3905
+ if (error instanceof RequestBodyError)
3906
+ return sendRequestError(res, error);
3907
+ return send(res, error instanceof EpEnvelopeError && error.code === "unimplemented" ? 422 : 400, { error: "invalid endpoint deregistration request" });
3908
+ }
3909
+ let owner;
3910
+ try {
3911
+ const verified = await verifyIdpToken(body.idpToken, ctx.bridgeIdp);
3912
+ owner = deriveOwnerForIdpSubject(ctx.ownerSecret, ctx.bridgeIdp.issuer, verified.sub);
3913
+ const row = findInteractiveActor(ctx.dir, owner, "cli");
3914
+ if (!row || !(row.scope ?? []).includes("supervise"))
3915
+ throw new Error("endpoint deregistration requires the current cli supervise grant");
3916
+ }
3917
+ catch {
3918
+ return refuseExchange(res, ctx, policy, peer, throttled, "endpoint deregistration proof or current grant refused", 403, { error: "endpoint deregistration proof or current grant refused" });
3919
+ }
3920
+ try {
3921
+ return send(res, 200, await ctx.deregisterEndpoint(body.request, { kind: "owner", owner }), { "cache-control": "no-store" });
3922
+ }
3923
+ catch {
3924
+ return send(res, 503, { error: "endpoint deregistration did not return a confirmed result; inspect the same registration before another request" });
3925
+ }
3926
+ }
3927
+ /** Authenticated manager issuance and maintenance on the selected exchange face. */
3878
3928
  export async function handleManagerServiceAuthority(req, res, ctx, policy) {
3879
3929
  if (req.method !== "POST")
3880
3930
  return send(res, 405, { error: "POST only" });
@@ -4074,6 +4124,7 @@ function makePublicHandler(ctx, policy, bundle) {
4074
4124
  ],
4075
4125
  ["/exchange", (req, res, c) => handleExchange(req, res, c, policy)],
4076
4126
  ["/manager-service-authority", (req, res, c) => handleManagerServiceAuthority(req, res, c, policy)],
4127
+ ["/endpoint-deregister", (req, res, c) => handleEndpointDeregister(req, res, c, policy)],
4077
4128
  ["/manager-instances", (req, res, c) => handleManagerInstances(req, res, c, policy)],
4078
4129
  [SERVICE_ENDPOINT_AUTHORITY_PATH, (req, res, c) => handleServiceEndpointAuthority(req, res, c, policy)],
4079
4130
  [SERVICE_ENDPOINT_HOST_PATH, (req, res, c) => handleServiceEndpointHost(req, res, c, policy)],