toolpass-client 0.12.0 → 0.13.0

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/README.md CHANGED
@@ -50,6 +50,8 @@ const d = await tp.check("dana@example.com", "jira-main", "DELETE_ISSUES", "issu
50
50
  d.decision, d.reason; // "deny", "denied: ..."
51
51
  d.identity; // the account the email resolved to (a GitHub login), "" if none
52
52
  await tp.require(...); // rejects with PermissionDenied unless allow
53
+ // The fifth argument: the user's groups, or { groups, fresh, token } (token: the user's own
54
+ // ID token, for a connection that reads the groups from it: oidc_issuer).
53
55
  ```
54
56
 
55
57
  Framework examples: [examples/agent-ts](https://github.com/roee-hersh/toolpass/tree/main/examples/agent-ts).
package/dist/index.d.ts CHANGED
@@ -91,8 +91,10 @@ export declare class Toolpass {
91
91
  /**
92
92
  * Ask toolpass. Never rejects on transport: every failure becomes an
93
93
  * `unknown` decision. The fifth argument is the user's groups, or an
94
- * options object: `{ groups, fresh }`. Groups left out or null are
95
- * omitted from the request.
94
+ * options object: `{ groups, fresh, token }`. Groups left out or null are
95
+ * omitted from the request. `token` is the user's own login token (an
96
+ * OIDC ID token), for a connection that reads the groups from it
97
+ * (`oidc_issuer`).
96
98
  *
97
99
  * `fresh: true` asks for an answer straight from the upstream system,
98
100
  * skipping toolpass's caches. Use it for destructive actions. It narrows
@@ -109,6 +111,8 @@ export declare class Toolpass {
109
111
  export interface CheckOptions {
110
112
  /** The user's groups; left out or null, none are sent. */
111
113
  groups?: readonly string[] | null;
114
+ /** The user's own login token (an OIDC ID token); left out or null, none is sent. */
115
+ token?: string | null;
112
116
  /** Skip toolpass's caches and ask the upstream system now. */
113
117
  fresh?: boolean;
114
118
  }
package/dist/index.js CHANGED
@@ -108,8 +108,10 @@ export class Toolpass {
108
108
  /**
109
109
  * Ask toolpass. Never rejects on transport: every failure becomes an
110
110
  * `unknown` decision. The fifth argument is the user's groups, or an
111
- * options object: `{ groups, fresh }`. Groups left out or null are
112
- * omitted from the request.
111
+ * options object: `{ groups, fresh, token }`. Groups left out or null are
112
+ * omitted from the request. `token` is the user's own login token (an
113
+ * OIDC ID token), for a connection that reads the groups from it
114
+ * (`oidc_issuer`).
113
115
  *
114
116
  * `fresh: true` asks for an answer straight from the upstream system,
115
117
  * skipping toolpass's caches. Use it for destructive actions. It narrows
@@ -117,7 +119,7 @@ export class Toolpass {
117
119
  * two; it does not close it.
118
120
  */
119
121
  async check(user, connection, action, resource, options) {
120
- const { groups, fresh } = checkOptions(options);
122
+ const { groups, fresh, token } = checkOptions(options);
121
123
  const body = { user, connection, action, resource };
122
124
  if (groups != null) {
123
125
  body.groups = groupList(groups);
@@ -125,6 +127,9 @@ export class Toolpass {
125
127
  if (fresh) {
126
128
  body.fresh = true;
127
129
  }
130
+ if (token) {
131
+ body.token = token;
132
+ }
128
133
  let response;
129
134
  let raw;
130
135
  try {
@@ -206,28 +211,31 @@ function isLoopback(hostname) {
206
211
  }
207
212
  function checkOptions(o) {
208
213
  if (o == null) {
209
- return { groups: null, fresh: false };
214
+ return { groups: null, fresh: false, token: "" };
210
215
  }
211
216
  if (Array.isArray(o)) {
212
- return { groups: o, fresh: false };
217
+ return { groups: o, fresh: false, token: "" };
213
218
  }
214
219
  if (typeof o === "boolean") {
215
- throw new TypeError("the fifth argument is the groups array or { groups, fresh }; pass { fresh: true } for a fresh check");
220
+ throw new TypeError("the fifth argument is the groups array or { groups, fresh, token }; pass { fresh: true } for a fresh check");
216
221
  }
217
222
  if (typeof o !== "object" || Symbol.iterator in o) {
218
223
  // A Set or another iterable of groups is not an array of them.
219
224
  throw new TypeError("groups must be an array of strings");
220
225
  }
221
226
  for (const key of Object.keys(o)) {
222
- if (key !== "groups" && key !== "fresh") {
223
- throw new TypeError(`unknown check option ${JSON.stringify(key)}; the options are groups and fresh`);
227
+ if (key !== "groups" && key !== "fresh" && key !== "token") {
228
+ throw new TypeError(`unknown check option ${JSON.stringify(key)}; the options are groups, fresh and token`);
224
229
  }
225
230
  }
226
231
  const opts = o;
227
232
  if (opts.fresh !== undefined && typeof opts.fresh !== "boolean") {
228
233
  throw new TypeError("fresh must be a boolean");
229
234
  }
230
- return { groups: opts.groups ?? null, fresh: opts.fresh === true };
235
+ if (opts.token != null && typeof opts.token !== "string") {
236
+ throw new TypeError("token must be a string");
237
+ }
238
+ return { groups: opts.groups ?? null, fresh: opts.fresh === true, token: opts.token ?? "" };
231
239
  }
232
240
  function groupList(groups) {
233
241
  if (typeof groups === "string" || !Array.isArray(groups) || !groups.every((g) => typeof g === "string")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolpass-client",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Check with toolpass whether a user may do something before your AI agent does it for them.",
5
5
  "keywords": [
6
6
  "ai-agents",