@auth0/ai 1.0.2 → 2.0.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 +1 -1
- package/dist/cjs/authorizers/ciba/CIBAAuthorizerBase.js +81 -55
- package/dist/cjs/authorizers/ciba/CIBAAuthorizerBase.js.map +1 -1
- package/dist/cjs/authorizers/ciba/CIBAAuthorizerParams.js.map +1 -1
- package/dist/cjs/authorizers/ciba/asyncLocalStorage.js +1 -1
- package/dist/cjs/authorizers/ciba/asyncLocalStorage.js.map +1 -1
- package/dist/cjs/authorizers/context.js +20 -0
- package/dist/cjs/authorizers/context.js.map +1 -0
- package/dist/cjs/authorizers/device-authorizer/DeviceAuthResponse.js +18 -0
- package/dist/cjs/authorizers/device-authorizer/DeviceAuthResponse.js.map +1 -0
- package/dist/cjs/authorizers/device-authorizer/DeviceAuthorizerBase.js +231 -0
- package/dist/cjs/authorizers/device-authorizer/DeviceAuthorizerBase.js.map +1 -0
- package/dist/cjs/{TokenResponse.js → authorizers/device-authorizer/DeviceAuthorizerParams.js} +1 -1
- package/dist/cjs/authorizers/device-authorizer/DeviceAuthorizerParams.js.map +1 -0
- package/dist/cjs/authorizers/device-authorizer/asyncLocalStorage.js +14 -0
- package/dist/cjs/authorizers/device-authorizer/asyncLocalStorage.js.map +1 -0
- package/dist/cjs/authorizers/device-authorizer/index.js +9 -0
- package/dist/cjs/authorizers/device-authorizer/index.js.map +1 -0
- package/dist/cjs/authorizers/federated-connections/FederatedConnectionAuthorizerBase.js +158 -0
- package/dist/cjs/authorizers/federated-connections/FederatedConnectionAuthorizerBase.js.map +1 -0
- package/dist/cjs/authorizers/federated-connections/FederatedConnectionAuthorizerParams.js +3 -0
- package/dist/cjs/authorizers/federated-connections/FederatedConnectionAuthorizerParams.js.map +1 -0
- package/dist/cjs/authorizers/federated-connections/asyncLocalStorage.js +5 -5
- package/dist/cjs/authorizers/federated-connections/asyncLocalStorage.js.map +1 -1
- package/dist/cjs/authorizers/federated-connections/index.js +5 -125
- package/dist/cjs/authorizers/federated-connections/index.js.map +1 -1
- package/dist/cjs/authorizers/index.js.map +1 -1
- package/dist/cjs/authorizers/types.js +14 -0
- package/dist/cjs/authorizers/types.js.map +1 -0
- package/dist/cjs/credentials.js +13 -0
- package/dist/cjs/credentials.js.map +1 -1
- package/dist/cjs/index.js +0 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/interrupts/DeviceInterrupts.js +79 -0
- package/dist/cjs/interrupts/DeviceInterrupts.js.map +1 -0
- package/dist/cjs/interrupts/index.js +37 -1
- package/dist/cjs/interrupts/index.js.map +1 -1
- package/dist/cjs/stores/Store.js +12 -0
- package/dist/cjs/stores/Store.js.map +1 -0
- package/dist/cjs/stores/SubStore.js +52 -0
- package/dist/cjs/stores/SubStore.js.map +1 -0
- package/dist/cjs/stores/impl/FSStore.js +80 -0
- package/dist/cjs/stores/impl/FSStore.js.map +1 -0
- package/dist/cjs/stores/impl/MemoryStore.js +15 -0
- package/dist/cjs/stores/impl/MemoryStore.js.map +1 -0
- package/dist/cjs/stores/impl/TempBoxStore.js +22 -0
- package/dist/cjs/stores/impl/TempBoxStore.js.map +1 -0
- package/dist/cjs/stores/index.js +10 -0
- package/dist/cjs/stores/index.js.map +1 -0
- package/dist/cjs/util/index.js +12 -0
- package/dist/cjs/util/index.js.map +1 -0
- package/dist/esm/authorizers/ciba/CIBAAuthorizerBase.d.ts +12 -8
- package/dist/esm/authorizers/ciba/CIBAAuthorizerBase.js +78 -55
- package/dist/esm/authorizers/ciba/CIBAAuthorizerBase.js.map +1 -1
- package/dist/esm/authorizers/ciba/CIBAAuthorizerParams.d.ts +20 -20
- package/dist/esm/authorizers/ciba/CIBAAuthorizerParams.js.map +1 -1
- package/dist/esm/authorizers/ciba/asyncLocalStorage.d.ts +7 -3
- package/dist/esm/authorizers/ciba/asyncLocalStorage.js +1 -1
- package/dist/esm/authorizers/ciba/asyncLocalStorage.js.map +1 -1
- package/dist/esm/authorizers/context.d.ts +16 -0
- package/dist/esm/authorizers/context.js +16 -0
- package/dist/esm/authorizers/context.js.map +1 -0
- package/dist/esm/authorizers/device-authorizer/DeviceAuthResponse.d.ts +38 -0
- package/dist/esm/authorizers/device-authorizer/DeviceAuthResponse.js +15 -0
- package/dist/esm/authorizers/device-authorizer/DeviceAuthResponse.js.map +1 -0
- package/dist/esm/authorizers/device-authorizer/DeviceAuthorizerBase.d.ts +49 -0
- package/dist/esm/authorizers/device-authorizer/DeviceAuthorizerBase.js +224 -0
- package/dist/esm/authorizers/device-authorizer/DeviceAuthorizerBase.js.map +1 -0
- package/dist/esm/authorizers/device-authorizer/DeviceAuthorizerParams.d.ts +58 -0
- package/dist/esm/authorizers/device-authorizer/DeviceAuthorizerParams.js +2 -0
- package/dist/esm/authorizers/device-authorizer/DeviceAuthorizerParams.js.map +1 -0
- package/dist/esm/authorizers/device-authorizer/asyncLocalStorage.d.ts +16 -0
- package/dist/esm/authorizers/device-authorizer/asyncLocalStorage.js +10 -0
- package/dist/esm/authorizers/device-authorizer/asyncLocalStorage.js.map +1 -0
- package/dist/esm/authorizers/device-authorizer/index.d.ts +4 -0
- package/dist/esm/authorizers/device-authorizer/index.js +3 -0
- package/dist/esm/authorizers/device-authorizer/index.js.map +1 -0
- package/dist/esm/authorizers/federated-connections/FederatedConnectionAuthorizerBase.d.ts +30 -0
- package/dist/esm/authorizers/federated-connections/FederatedConnectionAuthorizerBase.js +151 -0
- package/dist/esm/authorizers/federated-connections/FederatedConnectionAuthorizerBase.js.map +1 -0
- package/dist/esm/authorizers/federated-connections/FederatedConnectionAuthorizerParams.d.ts +38 -0
- package/dist/esm/authorizers/federated-connections/FederatedConnectionAuthorizerParams.js +2 -0
- package/dist/esm/authorizers/federated-connections/FederatedConnectionAuthorizerParams.js.map +1 -0
- package/dist/esm/authorizers/federated-connections/asyncLocalStorage.d.ts +7 -5
- package/dist/esm/authorizers/federated-connections/asyncLocalStorage.js +3 -3
- package/dist/esm/authorizers/federated-connections/asyncLocalStorage.js.map +1 -1
- package/dist/esm/authorizers/federated-connections/index.d.ts +3 -36
- package/dist/esm/authorizers/federated-connections/index.js +2 -122
- package/dist/esm/authorizers/federated-connections/index.js.map +1 -1
- package/dist/esm/authorizers/index.d.ts +1 -12
- package/dist/esm/authorizers/index.js.map +1 -1
- package/dist/esm/authorizers/types.d.ts +37 -0
- package/dist/esm/authorizers/types.js +11 -0
- package/dist/esm/authorizers/types.js.map +1 -0
- package/dist/esm/credentials.d.ts +33 -8
- package/dist/esm/credentials.js +11 -1
- package/dist/esm/credentials.js.map +1 -1
- package/dist/esm/index.d.ts +1 -3
- package/dist/esm/index.js +0 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/interrupts/DeviceInterrupts.d.ts +55 -0
- package/dist/esm/interrupts/DeviceInterrupts.js +70 -0
- package/dist/esm/interrupts/DeviceInterrupts.js.map +1 -0
- package/dist/esm/interrupts/index.d.ts +2 -0
- package/dist/esm/interrupts/index.js +2 -0
- package/dist/esm/interrupts/index.js.map +1 -1
- package/dist/esm/stores/Store.d.ts +40 -0
- package/dist/esm/stores/Store.js +11 -0
- package/dist/esm/stores/Store.js.map +1 -0
- package/dist/esm/stores/SubStore.d.ts +31 -0
- package/dist/esm/stores/SubStore.js +48 -0
- package/dist/esm/stores/SubStore.js.map +1 -0
- package/dist/esm/stores/impl/FSStore.d.ts +19 -0
- package/dist/esm/stores/impl/FSStore.js +72 -0
- package/dist/esm/stores/impl/FSStore.js.map +1 -0
- package/dist/esm/stores/impl/MemoryStore.d.ts +8 -0
- package/dist/esm/stores/impl/MemoryStore.js +11 -0
- package/dist/esm/stores/impl/MemoryStore.js.map +1 -0
- package/dist/esm/stores/impl/TempBoxStore.d.ts +13 -0
- package/dist/esm/stores/impl/TempBoxStore.js +18 -0
- package/dist/esm/stores/impl/TempBoxStore.js.map +1 -0
- package/dist/esm/stores/index.d.ts +5 -0
- package/dist/esm/stores/index.js +4 -0
- package/dist/esm/stores/index.js.map +1 -0
- package/dist/esm/util/index.d.ts +3 -0
- package/dist/esm/util/index.js +8 -0
- package/dist/esm/util/index.js.map +1 -0
- package/package.json +20 -4
- package/dist/cjs/TokenResponse.js.map +0 -1
- package/dist/cjs/authorizers/device-authorizer.js +0 -130
- package/dist/cjs/authorizers/device-authorizer.js.map +0 -1
- package/dist/esm/TokenResponse.d.ts +0 -15
- package/dist/esm/TokenResponse.js +0 -2
- package/dist/esm/TokenResponse.js.map +0 -1
- package/dist/esm/authorizers/device-authorizer.d.ts +0 -15
- package/dist/esm/authorizers/device-authorizer.js +0 -90
- package/dist/esm/authorizers/device-authorizer.js.map +0 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { discovery, initiateDeviceAuthorization } from "openid-client";
|
|
3
|
+
import { stableHash } from "stable-hash";
|
|
4
|
+
import { tokenSetFromTokenResponse } from "../../credentials";
|
|
5
|
+
import { AccessDeniedInterrupt, AuthorizationPendingInterrupt, AuthorizationPollingInterrupt, AuthorizationRequestExpiredInterrupt, } from "../../interrupts/DeviceInterrupts";
|
|
6
|
+
import { SubStore } from "../../stores";
|
|
7
|
+
import { omit } from "../../util";
|
|
8
|
+
import { nsFromContext } from "../context";
|
|
9
|
+
import { Auth0PublicClientSchema } from "../types";
|
|
10
|
+
import { asyncLocalStorage } from "./asyncLocalStorage";
|
|
11
|
+
import { parseDeviceAuthResponse, } from "./DeviceAuthResponse";
|
|
12
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
+
/**
|
|
14
|
+
* The DeviceAuthorizerBase class acts as a guard and wrapper around tools or agent
|
|
15
|
+
* capabilities that require authenticated access. It encapsulates the
|
|
16
|
+
* OpenID Connect Device Authorization Flow, allowing tools to interrupt execution
|
|
17
|
+
* to request user authorization and resume seamlessly once authorization is granted.
|
|
18
|
+
*
|
|
19
|
+
* Enable agentic tools to be wrapped with secure, lazy-triggered device authorization,
|
|
20
|
+
* with a clean interrupt/resume model for user-in-the-loop authentication.
|
|
21
|
+
*
|
|
22
|
+
* Core Concepts
|
|
23
|
+
*
|
|
24
|
+
* - Interrupt-driven flow: If no valid token is present, the tool throws a DeviceAuthInterrupt,
|
|
25
|
+
* which contains the verification_uri, user_code, and other relevant metadata.
|
|
26
|
+
*
|
|
27
|
+
* - Polling/resumption: The Agent client can poll the agent to resume execution.
|
|
28
|
+
*
|
|
29
|
+
* - Token management: Tokens (and refresh tokens, if applicable) are stored and reused
|
|
30
|
+
* transparently.
|
|
31
|
+
*
|
|
32
|
+
*/
|
|
33
|
+
export class DeviceAuthorizerBase {
|
|
34
|
+
constructor(auth0, params) {
|
|
35
|
+
this.auth0 = Auth0PublicClientSchema.parse(auth0);
|
|
36
|
+
if (!this.auth0.domain) {
|
|
37
|
+
throw new Error("Domain is required.");
|
|
38
|
+
}
|
|
39
|
+
if (!this.auth0.clientId) {
|
|
40
|
+
throw new Error("Client ID is required.");
|
|
41
|
+
}
|
|
42
|
+
if (!params.store) {
|
|
43
|
+
throw new Error("Store is required.");
|
|
44
|
+
}
|
|
45
|
+
this.params = {
|
|
46
|
+
credentialsContext: "thread",
|
|
47
|
+
...params,
|
|
48
|
+
};
|
|
49
|
+
const instanceID = this.getInstanceID();
|
|
50
|
+
this.credentialsStore = new SubStore(params.store, {
|
|
51
|
+
baseNamespace: [instanceID, "Credentials"],
|
|
52
|
+
getTTL: (credentials) => credentials.expiresIn ? credentials.expiresIn * 1000 : undefined,
|
|
53
|
+
});
|
|
54
|
+
this.authResponseStore = new SubStore(params.store, {
|
|
55
|
+
baseNamespace: [instanceID, "AuthResponses"],
|
|
56
|
+
getTTL: (authResponse) => authResponse.expiresIn ? authResponse.expiresIn * 1000 : undefined,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async start() {
|
|
60
|
+
var _a;
|
|
61
|
+
const requestedAt = Date.now() / 1000;
|
|
62
|
+
const config = await discovery(new URL(`https://${this.auth0.domain}`), this.auth0.clientId);
|
|
63
|
+
const handle = await initiateDeviceAuthorization(config, {
|
|
64
|
+
scope: ((_a = this.params.scopes) !== null && _a !== void 0 ? _a : []).join(" "),
|
|
65
|
+
audience: this.params.audience,
|
|
66
|
+
});
|
|
67
|
+
return parseDeviceAuthResponse(handle, requestedAt);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
*
|
|
71
|
+
* Wraps the execute method of a AI tool to handle CIBA authorization.
|
|
72
|
+
*
|
|
73
|
+
* @param getContext - A function that returns the context of the tool execution.
|
|
74
|
+
* @param execute - The tool execute method.
|
|
75
|
+
* @returns The wrapped execute method.
|
|
76
|
+
*/
|
|
77
|
+
protect(getContext, execute) {
|
|
78
|
+
return async (...args) => {
|
|
79
|
+
let authResponse;
|
|
80
|
+
if (asyncLocalStorage.getStore()) {
|
|
81
|
+
throw new Error("Cannot nest tool calls that require CIBA authorization.");
|
|
82
|
+
}
|
|
83
|
+
const context = getContext(...args);
|
|
84
|
+
const storeValue = {
|
|
85
|
+
args,
|
|
86
|
+
context: context,
|
|
87
|
+
};
|
|
88
|
+
return asyncLocalStorage.run(storeValue, async () => {
|
|
89
|
+
let credentials;
|
|
90
|
+
let credentialsObtained = false;
|
|
91
|
+
const authResponseNS = nsFromContext("tool-call", context);
|
|
92
|
+
const credentialsNS = nsFromContext(this.params.credentialsContext, context);
|
|
93
|
+
try {
|
|
94
|
+
credentials = await this.credentialsStore.get(credentialsNS, "credential");
|
|
95
|
+
if (!credentials) {
|
|
96
|
+
if (!this.params.onAuthorizationRequest ||
|
|
97
|
+
this.params.onAuthorizationRequest === "interrupt") {
|
|
98
|
+
authResponse = await this.authResponseStore.get(authResponseNS, "authResponse");
|
|
99
|
+
if (!authResponse) {
|
|
100
|
+
//Initial request
|
|
101
|
+
authResponse = await this.start();
|
|
102
|
+
await this.authResponseStore.put(authResponseNS, "authResponse", authResponse);
|
|
103
|
+
}
|
|
104
|
+
credentials = await this.getCredentials(authResponse);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// Block mode
|
|
108
|
+
authResponse = await this.start();
|
|
109
|
+
const credentialsPromise = this.getCredentialsPolling(authResponse);
|
|
110
|
+
if (typeof this.params.onAuthorizationRequest === "function") {
|
|
111
|
+
await this.params.onAuthorizationRequest(authResponse, credentialsPromise);
|
|
112
|
+
}
|
|
113
|
+
credentials = await credentialsPromise;
|
|
114
|
+
}
|
|
115
|
+
if (typeof credentials !== "undefined") {
|
|
116
|
+
this.credentialsStore.put(credentialsNS, "credential", credentials);
|
|
117
|
+
}
|
|
118
|
+
credentialsObtained = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const shouldInterrupt = err instanceof AuthorizationPendingInterrupt ||
|
|
123
|
+
err instanceof AuthorizationPollingInterrupt;
|
|
124
|
+
if (shouldInterrupt) {
|
|
125
|
+
return this.handleAuthorizationInterrupts(err);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
await this.deleteAuthRequest();
|
|
129
|
+
if (typeof this.params.onUnauthorized === "function") {
|
|
130
|
+
return this.params.onUnauthorized(err, ...args);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
return err;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (credentialsObtained) {
|
|
138
|
+
await this.deleteAuthRequest();
|
|
139
|
+
}
|
|
140
|
+
storeValue.credentials = credentials;
|
|
141
|
+
return execute(...args);
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async getCredentials(authResponse) {
|
|
146
|
+
var _a;
|
|
147
|
+
//TODO: this is not supported neither by the OIDC client library
|
|
148
|
+
// or the auth0 client.
|
|
149
|
+
const url = `https://${this.auth0.domain}/oauth/token`;
|
|
150
|
+
const response = await fetch(url, {
|
|
151
|
+
method: "POST",
|
|
152
|
+
headers: {
|
|
153
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
154
|
+
},
|
|
155
|
+
body: new URLSearchParams({
|
|
156
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
157
|
+
device_code: authResponse.deviceCode,
|
|
158
|
+
client_id: this.auth0.clientId,
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
if ((_a = response.headers.get("content-type")) === null || _a === void 0 ? void 0 : _a.includes("application/json")) {
|
|
163
|
+
const { error, error_description } = await response.json();
|
|
164
|
+
if (error === "authorization_pending") {
|
|
165
|
+
throw new AuthorizationPendingInterrupt(error_description, authResponse);
|
|
166
|
+
}
|
|
167
|
+
if (error === "slow_down") {
|
|
168
|
+
throw new AuthorizationPollingInterrupt(error_description, authResponse);
|
|
169
|
+
}
|
|
170
|
+
if (error === "expired_token") {
|
|
171
|
+
throw new AuthorizationRequestExpiredInterrupt(error_description, authResponse);
|
|
172
|
+
}
|
|
173
|
+
if (error === "access_denied") {
|
|
174
|
+
throw new AccessDeniedInterrupt(error_description, authResponse);
|
|
175
|
+
}
|
|
176
|
+
throw new Error(error_description);
|
|
177
|
+
}
|
|
178
|
+
const errText = await response.text();
|
|
179
|
+
throw new Error(`Failed to obtain tokens: ${errText}`);
|
|
180
|
+
}
|
|
181
|
+
const tr = await response.json();
|
|
182
|
+
const credentials = tokenSetFromTokenResponse(tr);
|
|
183
|
+
return credentials;
|
|
184
|
+
}
|
|
185
|
+
async getCredentialsPolling(authResponse) {
|
|
186
|
+
var _a;
|
|
187
|
+
let credentials = undefined;
|
|
188
|
+
do {
|
|
189
|
+
try {
|
|
190
|
+
credentials = await this.getCredentials(authResponse);
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
if (err instanceof AuthorizationPendingInterrupt ||
|
|
194
|
+
err instanceof AuthorizationPollingInterrupt) {
|
|
195
|
+
await sleep(((_a = err.response.interval) !== null && _a !== void 0 ? _a : 10) * 1000);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
throw err;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} while (!credentials);
|
|
202
|
+
return credentials;
|
|
203
|
+
}
|
|
204
|
+
getInstanceID() {
|
|
205
|
+
const props = {
|
|
206
|
+
auth0: this.auth0,
|
|
207
|
+
params: omit(this.params, ["store", "onUnauthorized"]),
|
|
208
|
+
};
|
|
209
|
+
const sh = stableHash(props);
|
|
210
|
+
return crypto.createHash("MD5").update(sh).digest("hex");
|
|
211
|
+
}
|
|
212
|
+
deleteAuthRequest() {
|
|
213
|
+
const store = asyncLocalStorage.getStore();
|
|
214
|
+
if (!store) {
|
|
215
|
+
throw new Error("This method should be called from within a tool.");
|
|
216
|
+
}
|
|
217
|
+
const authResponseNS = nsFromContext("tool-call", store.context);
|
|
218
|
+
return this.authResponseStore.delete(authResponseNS, "authResponse");
|
|
219
|
+
}
|
|
220
|
+
handleAuthorizationInterrupts(err) {
|
|
221
|
+
throw err;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=DeviceAuthorizerBase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeviceAuthorizerBase.js","sourceRoot":"","sources":["../../../../src/authorizers/device-authorizer/DeviceAuthorizerBase.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from \"node:crypto\";\nimport { discovery, initiateDeviceAuthorization } from \"openid-client\";\nimport { stableHash } from \"stable-hash\";\n\nimport { TokenSet, tokenSetFromTokenResponse } from \"../../credentials\";\nimport {\n AccessDeniedInterrupt,\n AuthorizationPendingInterrupt,\n AuthorizationPollingInterrupt,\n AuthorizationRequestExpiredInterrupt,\n} from \"../../interrupts/DeviceInterrupts\";\nimport { Store, SubStore } from \"../../stores\";\nimport { omit } from \"../../util\";\nimport { AuthContext, ContextGetter, nsFromContext } from \"../context\";\nimport { Auth0PublicClientParams, Auth0PublicClientSchema } from \"../types\";\nimport { asyncLocalStorage, AsyncStorageValue } from \"./asyncLocalStorage\";\nimport { DeviceAuthorizerParams } from \"./DeviceAuthorizerParams\";\nimport {\n DeviceAuthorizationResponse,\n parseDeviceAuthResponse,\n} from \"./DeviceAuthResponse\";\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * The DeviceAuthorizerBase class acts as a guard and wrapper around tools or agent\n * capabilities that require authenticated access. It encapsulates the\n * OpenID Connect Device Authorization Flow, allowing tools to interrupt execution\n * to request user authorization and resume seamlessly once authorization is granted.\n *\n * Enable agentic tools to be wrapped with secure, lazy-triggered device authorization,\n * with a clean interrupt/resume model for user-in-the-loop authentication.\n *\n * Core Concepts\n *\n * - Interrupt-driven flow: If no valid token is present, the tool throws a DeviceAuthInterrupt,\n * which contains the verification_uri, user_code, and other relevant metadata.\n *\n * - Polling/resumption: The Agent client can poll the agent to resume execution.\n *\n * - Token management: Tokens (and refresh tokens, if applicable) are stored and reused\n * transparently.\n *\n */\nexport class DeviceAuthorizerBase<ToolExecuteArgs extends any[]> {\n private auth0: Auth0PublicClientParams;\n private params: DeviceAuthorizerParams<ToolExecuteArgs> & {\n credentialsContext: AuthContext;\n };\n private credentialsStore: Store<TokenSet>;\n private authResponseStore: Store<DeviceAuthorizationResponse>;\n\n constructor(\n auth0: Partial<Auth0PublicClientParams>,\n params: DeviceAuthorizerParams<ToolExecuteArgs>\n ) {\n this.auth0 = Auth0PublicClientSchema.parse(auth0);\n if (!this.auth0.domain) {\n throw new Error(\"Domain is required.\");\n }\n if (!this.auth0.clientId) {\n throw new Error(\"Client ID is required.\");\n }\n if (!params.store) {\n throw new Error(\"Store is required.\");\n }\n\n this.params = {\n credentialsContext: \"thread\",\n ...params,\n };\n\n const instanceID = this.getInstanceID();\n\n this.credentialsStore = new SubStore(params.store, {\n baseNamespace: [instanceID, \"Credentials\"],\n getTTL: (credentials) =>\n credentials.expiresIn ? credentials.expiresIn * 1000 : undefined,\n });\n\n this.authResponseStore = new SubStore(params.store, {\n baseNamespace: [instanceID, \"AuthResponses\"],\n getTTL: (authResponse) =>\n authResponse.expiresIn ? authResponse.expiresIn * 1000 : undefined,\n });\n }\n\n private async start(): Promise<DeviceAuthorizationResponse> {\n const requestedAt = Date.now() / 1000;\n const config = await discovery(\n new URL(`https://${this.auth0.domain!}`),\n this.auth0.clientId!\n );\n\n const handle = await initiateDeviceAuthorization(config, {\n scope: (this.params.scopes ?? []).join(\" \"),\n audience: this.params.audience!,\n });\n\n return parseDeviceAuthResponse(handle, requestedAt);\n }\n\n /**\n *\n * Wraps the execute method of a AI tool to handle CIBA authorization.\n *\n * @param getContext - A function that returns the context of the tool execution.\n * @param execute - The tool execute method.\n * @returns The wrapped execute method.\n */\n protect(\n getContext: ContextGetter<ToolExecuteArgs>,\n execute: (...args: ToolExecuteArgs) => any\n ): (...args: ToolExecuteArgs) => any {\n return async (...args: ToolExecuteArgs) => {\n let authResponse: DeviceAuthorizationResponse | undefined;\n if (asyncLocalStorage.getStore()) {\n throw new Error(\n \"Cannot nest tool calls that require CIBA authorization.\"\n );\n }\n\n const context = getContext(...args);\n const storeValue: AsyncStorageValue<any> = {\n args,\n context: context,\n };\n\n return asyncLocalStorage.run(storeValue, async () => {\n let credentials: TokenSet | undefined;\n let credentialsObtained = false;\n const authResponseNS = nsFromContext(\"tool-call\", context);\n const credentialsNS = nsFromContext(\n this.params.credentialsContext,\n context\n );\n try {\n credentials = await this.credentialsStore.get(\n credentialsNS,\n \"credential\"\n );\n\n if (!credentials) {\n if (\n !this.params.onAuthorizationRequest ||\n this.params.onAuthorizationRequest === \"interrupt\"\n ) {\n authResponse = await this.authResponseStore.get(\n authResponseNS,\n \"authResponse\"\n );\n\n if (!authResponse) {\n //Initial request\n authResponse = await this.start();\n await this.authResponseStore.put(\n authResponseNS,\n \"authResponse\",\n authResponse\n );\n }\n credentials = await this.getCredentials(authResponse);\n } else {\n // Block mode\n authResponse = await this.start();\n const credentialsPromise =\n this.getCredentialsPolling(authResponse);\n if (typeof this.params.onAuthorizationRequest === \"function\") {\n await this.params.onAuthorizationRequest(\n authResponse,\n credentialsPromise\n );\n }\n credentials = await credentialsPromise;\n }\n\n if (typeof credentials !== \"undefined\") {\n this.credentialsStore.put(\n credentialsNS,\n \"credential\",\n credentials\n );\n }\n\n credentialsObtained = true;\n }\n } catch (err) {\n const shouldInterrupt =\n err instanceof AuthorizationPendingInterrupt ||\n err instanceof AuthorizationPollingInterrupt;\n if (shouldInterrupt) {\n return this.handleAuthorizationInterrupts(err);\n } else {\n await this.deleteAuthRequest();\n if (typeof this.params.onUnauthorized === \"function\") {\n return this.params.onUnauthorized(err as Error, ...args);\n } else {\n return err;\n }\n }\n }\n if (credentialsObtained) {\n await this.deleteAuthRequest();\n }\n storeValue.credentials = credentials;\n return execute(...args);\n });\n };\n }\n\n private async getCredentials(\n authResponse: DeviceAuthorizationResponse\n ): Promise<TokenSet | undefined> {\n //TODO: this is not supported neither by the OIDC client library\n // or the auth0 client.\n const url = `https://${this.auth0.domain!}/oauth/token`;\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: new URLSearchParams({\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n device_code: authResponse.deviceCode,\n client_id: this.auth0.clientId!,\n }),\n });\n\n if (!response.ok) {\n if (response.headers.get(\"content-type\")?.includes(\"application/json\")) {\n const { error, error_description } = await response.json();\n if (error === \"authorization_pending\") {\n throw new AuthorizationPendingInterrupt(\n error_description,\n authResponse\n );\n }\n if (error === \"slow_down\") {\n throw new AuthorizationPollingInterrupt(\n error_description,\n authResponse\n );\n }\n if (error === \"expired_token\") {\n throw new AuthorizationRequestExpiredInterrupt(\n error_description,\n authResponse\n );\n }\n if (error === \"access_denied\") {\n throw new AccessDeniedInterrupt(error_description, authResponse);\n }\n throw new Error(error_description);\n }\n const errText = await response.text();\n throw new Error(`Failed to obtain tokens: ${errText}`);\n }\n\n const tr = await response.json();\n const credentials = tokenSetFromTokenResponse(tr);\n\n return credentials;\n }\n\n protected async getCredentialsPolling(\n authResponse: DeviceAuthorizationResponse\n ): Promise<TokenSet | undefined> {\n let credentials: TokenSet | undefined = undefined;\n\n do {\n try {\n credentials = await this.getCredentials(authResponse);\n } catch (err) {\n if (\n err instanceof AuthorizationPendingInterrupt ||\n err instanceof AuthorizationPollingInterrupt\n ) {\n await sleep((err.response.interval ?? 10) * 1000);\n } else {\n throw err;\n }\n }\n } while (!credentials);\n\n return credentials;\n }\n\n private getInstanceID(): string {\n const props = {\n auth0: this.auth0,\n params: omit(this.params, [\"store\", \"onUnauthorized\"]),\n };\n const sh = stableHash(props);\n return crypto.createHash(\"MD5\").update(sh).digest(\"hex\");\n }\n\n protected deleteAuthRequest() {\n const store = asyncLocalStorage.getStore();\n if (!store) {\n throw new Error(\"This method should be called from within a tool.\");\n }\n const authResponseNS = nsFromContext(\"tool-call\", store.context);\n return this.authResponseStore.delete(authResponseNS, \"authResponse\");\n }\n\n protected handleAuthorizationInterrupts(\n err: AuthorizationPendingInterrupt | AuthorizationPollingInterrupt\n ) {\n throw err;\n }\n}\n"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { TokenSet } from "../../credentials";
|
|
2
|
+
import { DeviceInterrupt } from "../../interrupts/DeviceInterrupts";
|
|
3
|
+
import { Store } from "../../stores";
|
|
4
|
+
import { AuthContext } from "../context";
|
|
5
|
+
import { OnAuthorizationRequest } from "../types";
|
|
6
|
+
import { DeviceAuthorizationResponse } from "./DeviceAuthResponse";
|
|
7
|
+
/**
|
|
8
|
+
* The parameters for the DeviceAuthorizer.
|
|
9
|
+
*/
|
|
10
|
+
export type DeviceAuthorizerParams<ToolExecuteArgs extends any[]> = {
|
|
11
|
+
/**
|
|
12
|
+
* The scopes to request.
|
|
13
|
+
*/
|
|
14
|
+
scopes: string[];
|
|
15
|
+
/**
|
|
16
|
+
* The audience to request.
|
|
17
|
+
*/
|
|
18
|
+
audience?: string;
|
|
19
|
+
/**
|
|
20
|
+
* The authorization response store.
|
|
21
|
+
* Allows to store the authorization response for resuming the tool execution
|
|
22
|
+
* after the interrupt.
|
|
23
|
+
*/
|
|
24
|
+
store: Store;
|
|
25
|
+
/**
|
|
26
|
+
* AuthContext defines the scope of credential sharing:
|
|
27
|
+
* - "tool-call": Credentials are valid only for a single invocation of the tool.
|
|
28
|
+
* - "tool": Credentials are shared across multiple calls to the same tool within the same thread.
|
|
29
|
+
* - "thread": Credentials are shared across all tools using the same authorizer within the current thread.
|
|
30
|
+
* - "agent": Credentials are shared globally across all threads and tools in the agent.
|
|
31
|
+
*
|
|
32
|
+
* @default "thread"
|
|
33
|
+
*/
|
|
34
|
+
credentialsContext?: AuthContext;
|
|
35
|
+
/**
|
|
36
|
+
* The behavior when the authorization request is made.
|
|
37
|
+
*
|
|
38
|
+
* - `block`: The tool execution is blocked until the user completes the authorization.
|
|
39
|
+
* - `interrupt`: The tool execution is interrupted until the user completes the authorization.
|
|
40
|
+
* - a callback: Same as "block" but give access to the auth request this allow you
|
|
41
|
+
* to show the link/code and open the browser
|
|
42
|
+
*
|
|
43
|
+
* Defaults to `interrupt`.
|
|
44
|
+
* Note: The block mode is only useful for development purposes and should not be used in production.
|
|
45
|
+
*/
|
|
46
|
+
onAuthorizationRequest?: OnAuthorizationRequest | ((authReq: DeviceAuthorizationResponse, poll: Promise<TokenSet | undefined>) => Promise<void>);
|
|
47
|
+
/**
|
|
48
|
+
*
|
|
49
|
+
* A callback that is called when the authorization request is unsuccessful.
|
|
50
|
+
*
|
|
51
|
+
* @param err - The error that occurred.
|
|
52
|
+
* @param args - The tool execution arguments.
|
|
53
|
+
* @returns - The final response the tool will return.
|
|
54
|
+
*/
|
|
55
|
+
onUnauthorized?: (err: Error | DeviceInterrupt, ...args: ToolExecuteArgs) => any;
|
|
56
|
+
};
|
|
57
|
+
export type { DeviceAuthorizationResponse };
|
|
58
|
+
//# sourceMappingURL=DeviceAuthorizerParams.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeviceAuthorizerParams.js","sourceRoot":"","sources":["../../../../src/authorizers/device-authorizer/DeviceAuthorizerParams.ts"],"names":[],"mappings":"","sourcesContent":["import { TokenSet } from \"../../credentials\";\nimport { DeviceInterrupt } from \"../../interrupts/DeviceInterrupts\";\nimport { Store } from \"../../stores\";\nimport { AuthContext } from \"../context\";\nimport { OnAuthorizationRequest } from \"../types\";\nimport { DeviceAuthorizationResponse } from \"./DeviceAuthResponse\";\n\n/**\n * The parameters for the DeviceAuthorizer.\n */\nexport type DeviceAuthorizerParams<ToolExecuteArgs extends any[]> = {\n /**\n * The scopes to request.\n */\n scopes: string[];\n\n /**\n * The audience to request.\n */\n audience?: string;\n\n /**\n * The authorization response store.\n * Allows to store the authorization response for resuming the tool execution\n * after the interrupt.\n */\n store: Store;\n\n /**\n * AuthContext defines the scope of credential sharing:\n * - \"tool-call\": Credentials are valid only for a single invocation of the tool.\n * - \"tool\": Credentials are shared across multiple calls to the same tool within the same thread.\n * - \"thread\": Credentials are shared across all tools using the same authorizer within the current thread.\n * - \"agent\": Credentials are shared globally across all threads and tools in the agent.\n *\n * @default \"thread\"\n */\n credentialsContext?: AuthContext;\n\n /**\n * The behavior when the authorization request is made.\n *\n * - `block`: The tool execution is blocked until the user completes the authorization.\n * - `interrupt`: The tool execution is interrupted until the user completes the authorization.\n * - a callback: Same as \"block\" but give access to the auth request this allow you\n * to show the link/code and open the browser\n *\n * Defaults to `interrupt`.\n * Note: The block mode is only useful for development purposes and should not be used in production.\n */\n onAuthorizationRequest?:\n | OnAuthorizationRequest\n | ((\n authReq: DeviceAuthorizationResponse,\n poll: Promise<TokenSet | undefined>\n ) => Promise<void>);\n\n /**\n *\n * A callback that is called when the authorization request is unsuccessful.\n *\n * @param err - The error that occurred.\n * @param args - The tool execution arguments.\n * @returns - The final response the tool will return.\n */\n onUnauthorized?: (\n err: Error | DeviceInterrupt,\n ...args: ToolExecuteArgs\n ) => any;\n};\n\nexport type { DeviceAuthorizationResponse };\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2
|
+
import { TokenSet } from "../../credentials";
|
|
3
|
+
export type AsyncStorageValue<TContext> = {
|
|
4
|
+
credentials?: TokenSet;
|
|
5
|
+
/**
|
|
6
|
+
* The tool execution context.
|
|
7
|
+
*/
|
|
8
|
+
context: TContext;
|
|
9
|
+
/**
|
|
10
|
+
* The tool execution arguments.
|
|
11
|
+
*/
|
|
12
|
+
args: any[];
|
|
13
|
+
};
|
|
14
|
+
export declare const asyncLocalStorage: AsyncLocalStorage<AsyncStorageValue<any>>;
|
|
15
|
+
export declare const getDeviceAuthorizerCredentials: () => TokenSet | undefined;
|
|
16
|
+
//# sourceMappingURL=asyncLocalStorage.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2
|
+
export const asyncLocalStorage = new AsyncLocalStorage();
|
|
3
|
+
export const getDeviceAuthorizerCredentials = () => {
|
|
4
|
+
const t = asyncLocalStorage.getStore();
|
|
5
|
+
if (typeof t === "undefined") {
|
|
6
|
+
throw new Error("The tool must be wrapped with the withAsyncUserConfirmation function.");
|
|
7
|
+
}
|
|
8
|
+
return t.credentials;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=asyncLocalStorage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asyncLocalStorage.js","sourceRoot":"","sources":["../../../../src/authorizers/device-authorizer/asyncLocalStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAkBhD,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,iBAAiB,EAEnD,CAAC;AAEJ,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAG,EAAE;IACjD,MAAM,CAAC,GAAG,iBAAiB,CAAC,QAAQ,EAAE,CAAC;IACvC,IAAI,OAAO,CAAC,KAAK,WAAW,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,uEAAuE,CACxE,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,CAAC,WAAW,CAAC;AACvB,CAAC,CAAC","sourcesContent":["import { AsyncLocalStorage } from \"async_hooks\";\n\nimport { TokenSet } from \"../../credentials\";\n\nexport type AsyncStorageValue<TContext> = {\n credentials?: TokenSet;\n\n /**\n * The tool execution context.\n */\n context: TContext;\n\n /**\n * The tool execution arguments.\n */\n args: any[];\n};\n\nexport const asyncLocalStorage = new AsyncLocalStorage<\n AsyncStorageValue<any>\n>();\n\nexport const getDeviceAuthorizerCredentials = () => {\n const t = asyncLocalStorage.getStore();\n if (typeof t === \"undefined\") {\n throw new Error(\n \"The tool must be wrapped with the withAsyncUserConfirmation function.\"\n );\n }\n return t.credentials;\n};\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { getDeviceAuthorizerCredentials, asyncLocalStorage, } from "./asyncLocalStorage";
|
|
2
|
+
export { DeviceAuthorizerBase } from "./DeviceAuthorizerBase";
|
|
3
|
+
export type { DeviceAuthorizerParams, DeviceAuthorizationResponse, } from "./DeviceAuthorizerParams";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/authorizers/device-authorizer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC","sourcesContent":["export {\n getDeviceAuthorizerCredentials,\n asyncLocalStorage,\n} from \"./asyncLocalStorage\";\n\nexport { DeviceAuthorizerBase } from \"./DeviceAuthorizerBase\";\n\nexport type {\n DeviceAuthorizerParams,\n DeviceAuthorizationResponse,\n} from \"./DeviceAuthorizerParams\";\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { TokenResponse, TokenSet } from "../../credentials";
|
|
2
|
+
import { Auth0Interrupt } from "../../interrupts";
|
|
3
|
+
import { ContextGetter } from "../context";
|
|
4
|
+
import { Auth0ClientParams } from "../types";
|
|
5
|
+
import { FederatedConnectionAuthorizerParams } from "./FederatedConnectionAuthorizerParams";
|
|
6
|
+
/**
|
|
7
|
+
* Requests authorization to a third party service via Federated Connection.
|
|
8
|
+
*/
|
|
9
|
+
export declare class FederatedConnectionAuthorizerBase<ToolExecuteArgs extends any[]> {
|
|
10
|
+
private readonly auth0;
|
|
11
|
+
private readonly params;
|
|
12
|
+
private readonly credentialsStore;
|
|
13
|
+
constructor(auth0: Partial<Auth0ClientParams>, params: FederatedConnectionAuthorizerParams<ToolExecuteArgs>);
|
|
14
|
+
private getInstanceID;
|
|
15
|
+
protected handleAuthorizationInterrupts(err: Auth0Interrupt): void;
|
|
16
|
+
protected validateToken(tokenResponse?: TokenResponse): void;
|
|
17
|
+
private getAccessTokenImpl;
|
|
18
|
+
protected getAccessToken(...toolContext: ToolExecuteArgs): Promise<TokenSet>;
|
|
19
|
+
protected getRefreshToken(...toolContext: ToolExecuteArgs): Promise<string | undefined>;
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* Wraps the execute method of an AI tool to handle Federated Connections authorization.
|
|
23
|
+
*
|
|
24
|
+
* @param getContext - A function that returns the context of the tool execution.
|
|
25
|
+
* @param execute - The tool execute method.
|
|
26
|
+
* @returns The wrapped execute method.
|
|
27
|
+
*/
|
|
28
|
+
protect(getContext: ContextGetter<ToolExecuteArgs>, execute: (...args: ToolExecuteArgs) => any): (...args: ToolExecuteArgs) => any;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=FederatedConnectionAuthorizerBase.d.ts.map
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import stableHash from "stable-hash";
|
|
3
|
+
import { tokenSetFromTokenResponse, } from "../../credentials";
|
|
4
|
+
import { Auth0Interrupt, FederatedConnectionError, FederatedConnectionInterrupt, } from "../../interrupts";
|
|
5
|
+
import { resolveParameter } from "../../parameters";
|
|
6
|
+
import { SubStore } from "../../stores";
|
|
7
|
+
import { omit } from "../../util";
|
|
8
|
+
import { nsFromContext } from "../context";
|
|
9
|
+
import { Auth0ClientSchema } from "../types";
|
|
10
|
+
import { asyncLocalStorage } from "./asyncLocalStorage";
|
|
11
|
+
/**
|
|
12
|
+
* Requests authorization to a third party service via Federated Connection.
|
|
13
|
+
*/
|
|
14
|
+
export class FederatedConnectionAuthorizerBase {
|
|
15
|
+
constructor(auth0, params) {
|
|
16
|
+
this.auth0 = Auth0ClientSchema.parse(auth0);
|
|
17
|
+
this.params = {
|
|
18
|
+
credentialsContext: "thread",
|
|
19
|
+
...params,
|
|
20
|
+
};
|
|
21
|
+
const instanceID = this.getInstanceID();
|
|
22
|
+
this.credentialsStore = new SubStore(params.store, {
|
|
23
|
+
baseNamespace: [instanceID, "Credentials"],
|
|
24
|
+
getTTL: (credentials) => credentials.expiresIn ? credentials.expiresIn * 1000 : undefined,
|
|
25
|
+
});
|
|
26
|
+
if (typeof params.refreshToken === "undefined" &&
|
|
27
|
+
typeof params.accessToken === "undefined") {
|
|
28
|
+
throw new Error("Either refreshToken or accessToken must be provided to initialize the Authorizer.");
|
|
29
|
+
}
|
|
30
|
+
if (typeof params.accessToken !== "undefined" &&
|
|
31
|
+
typeof params.refreshToken !== "undefined") {
|
|
32
|
+
throw new Error("Only one of refreshToken or accessToken can be provided to initialize the Authorizer.");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
getInstanceID() {
|
|
36
|
+
const props = {
|
|
37
|
+
auth0: this.auth0,
|
|
38
|
+
params: omit(this.params, ["store", "refreshToken", "accessToken"]),
|
|
39
|
+
};
|
|
40
|
+
const sh = stableHash(props);
|
|
41
|
+
return crypto.createHash("MD5").update(sh).digest("hex");
|
|
42
|
+
}
|
|
43
|
+
handleAuthorizationInterrupts(err) {
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
validateToken(tokenResponse) {
|
|
47
|
+
var _a;
|
|
48
|
+
const store = asyncLocalStorage.getStore();
|
|
49
|
+
if (!store) {
|
|
50
|
+
throw new Error("The tool must be wrapped with the FederationConnectionAuthorizer.");
|
|
51
|
+
}
|
|
52
|
+
const { scopes, connection } = store;
|
|
53
|
+
if (!tokenResponse) {
|
|
54
|
+
throw new FederatedConnectionInterrupt(`Authorization required to access the Federated Connection: ${this.params.connection}`, connection, scopes, scopes);
|
|
55
|
+
}
|
|
56
|
+
const currentScopes = ((_a = tokenResponse.scope) !== null && _a !== void 0 ? _a : "").split(" ");
|
|
57
|
+
const missingScopes = scopes.filter((s) => !currentScopes.includes(s));
|
|
58
|
+
store.currentScopes = currentScopes;
|
|
59
|
+
if (missingScopes.length > 0) {
|
|
60
|
+
throw new FederatedConnectionInterrupt(`Authorization required to access the Federated Connection: ${this.params.connection}. Missing scopes: ${missingScopes.join(", ")}`, connection, scopes, [...currentScopes, ...scopes]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async getAccessTokenImpl(...toolContext) {
|
|
64
|
+
const store = asyncLocalStorage.getStore();
|
|
65
|
+
if (!store) {
|
|
66
|
+
throw new Error("The tool must be wrapped with the FederationConnectionAuthorizer.");
|
|
67
|
+
}
|
|
68
|
+
const { connection } = store;
|
|
69
|
+
const subjectToken = await this.getRefreshToken(...toolContext);
|
|
70
|
+
if (!subjectToken) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const exchangeParams = {
|
|
74
|
+
grant_type: "urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token",
|
|
75
|
+
client_id: this.auth0.clientId,
|
|
76
|
+
client_secret: this.auth0.clientSecret,
|
|
77
|
+
subject_token_type: "urn:ietf:params:oauth:token-type:refresh_token",
|
|
78
|
+
subject_token: subjectToken,
|
|
79
|
+
connection: connection,
|
|
80
|
+
requested_token_type: "http://auth0.com/oauth/token-type/federated-connection-access-token",
|
|
81
|
+
};
|
|
82
|
+
const res = await fetch(`https://${this.auth0.domain}/oauth/token`, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: { "Content-Type": "application/json" },
|
|
85
|
+
body: JSON.stringify(exchangeParams),
|
|
86
|
+
});
|
|
87
|
+
if (!res.ok) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const tokenResponse = await res.json();
|
|
91
|
+
return tokenResponse;
|
|
92
|
+
}
|
|
93
|
+
async getAccessToken(...toolContext) {
|
|
94
|
+
let tokenResponse;
|
|
95
|
+
if (typeof this.params.refreshToken === "function") {
|
|
96
|
+
tokenResponse = await this.getAccessTokenImpl(...toolContext);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
tokenResponse = await resolveParameter(this.params.accessToken, toolContext);
|
|
100
|
+
}
|
|
101
|
+
this.validateToken(tokenResponse);
|
|
102
|
+
return tokenSetFromTokenResponse(tokenResponse);
|
|
103
|
+
}
|
|
104
|
+
async getRefreshToken(...toolContext) {
|
|
105
|
+
return await resolveParameter(this.params.refreshToken, toolContext);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
*
|
|
109
|
+
* Wraps the execute method of an AI tool to handle Federated Connections authorization.
|
|
110
|
+
*
|
|
111
|
+
* @param getContext - A function that returns the context of the tool execution.
|
|
112
|
+
* @param execute - The tool execute method.
|
|
113
|
+
* @returns The wrapped execute method.
|
|
114
|
+
*/
|
|
115
|
+
protect(getContext, execute) {
|
|
116
|
+
return async (...args) => {
|
|
117
|
+
const context = getContext(...args);
|
|
118
|
+
const asyncStore = {
|
|
119
|
+
context: context,
|
|
120
|
+
scopes: this.params.scopes,
|
|
121
|
+
connection: this.params.connection,
|
|
122
|
+
};
|
|
123
|
+
if (asyncLocalStorage.getStore()) {
|
|
124
|
+
throw new Error("Cannot nest tool calls that require federated connection authorization.");
|
|
125
|
+
}
|
|
126
|
+
return asyncLocalStorage.run(asyncStore, async () => {
|
|
127
|
+
const credentialsNS = nsFromContext(this.params.credentialsContext, context);
|
|
128
|
+
try {
|
|
129
|
+
let credentials = await this.credentialsStore.get(credentialsNS, "credential");
|
|
130
|
+
if (!credentials) {
|
|
131
|
+
credentials = await this.getAccessToken(...args);
|
|
132
|
+
await this.credentialsStore.put(credentialsNS, "credential", credentials);
|
|
133
|
+
}
|
|
134
|
+
asyncStore.credentials = credentials;
|
|
135
|
+
return await execute(...args);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
if (err instanceof FederatedConnectionError) {
|
|
139
|
+
const interrupt = new FederatedConnectionInterrupt(err.message, asyncStore.connection, asyncStore.scopes, asyncStore.scopes);
|
|
140
|
+
return this.handleAuthorizationInterrupts(interrupt);
|
|
141
|
+
}
|
|
142
|
+
if (err instanceof Auth0Interrupt) {
|
|
143
|
+
return this.handleAuthorizationInterrupts(err);
|
|
144
|
+
}
|
|
145
|
+
throw err;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=FederatedConnectionAuthorizerBase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FederatedConnectionAuthorizerBase.js","sourceRoot":"","sources":["../../../../src/authorizers/federated-connections/FederatedConnectionAuthorizerBase.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from \"crypto\";\nimport stableHash from \"stable-hash\";\n\nimport {\n TokenResponse,\n TokenSet,\n tokenSetFromTokenResponse,\n} from \"../../credentials\";\nimport {\n Auth0Interrupt,\n FederatedConnectionError,\n FederatedConnectionInterrupt,\n} from \"../../interrupts\";\nimport { resolveParameter } from \"../../parameters\";\nimport { SubStore } from \"../../stores\";\nimport { omit, RequireFields } from \"../../util\";\nimport { ContextGetter, nsFromContext } from \"../context\";\nimport { Auth0ClientParams, Auth0ClientSchema } from \"../types\";\nimport { asyncLocalStorage, AsyncStorageValue } from \"./asyncLocalStorage\";\nimport { FederatedConnectionAuthorizerParams } from \"./FederatedConnectionAuthorizerParams\";\n\n/**\n * Requests authorization to a third party service via Federated Connection.\n */\n\nexport class FederatedConnectionAuthorizerBase<ToolExecuteArgs extends any[]> {\n private readonly auth0: Auth0ClientParams;\n private readonly params: RequireFields<\n FederatedConnectionAuthorizerParams<ToolExecuteArgs>,\n \"credentialsContext\"\n >;\n private readonly credentialsStore: SubStore<TokenSet>;\n\n constructor(\n auth0: Partial<Auth0ClientParams>,\n params: FederatedConnectionAuthorizerParams<ToolExecuteArgs>\n ) {\n this.auth0 = Auth0ClientSchema.parse(auth0);\n this.params = {\n credentialsContext: \"thread\",\n ...params,\n };\n\n const instanceID = this.getInstanceID();\n\n this.credentialsStore = new SubStore<TokenSet>(params.store, {\n baseNamespace: [instanceID, \"Credentials\"],\n getTTL: (credentials) =>\n credentials.expiresIn ? credentials.expiresIn * 1000 : undefined,\n });\n\n if (\n typeof params.refreshToken === \"undefined\" &&\n typeof params.accessToken === \"undefined\"\n ) {\n throw new Error(\n \"Either refreshToken or accessToken must be provided to initialize the Authorizer.\"\n );\n }\n\n if (\n typeof params.accessToken !== \"undefined\" &&\n typeof params.refreshToken !== \"undefined\"\n ) {\n throw new Error(\n \"Only one of refreshToken or accessToken can be provided to initialize the Authorizer.\"\n );\n }\n }\n\n private getInstanceID(): string {\n const props = {\n auth0: this.auth0,\n params: omit(this.params, [\"store\", \"refreshToken\", \"accessToken\"]),\n };\n const sh = stableHash(props);\n return crypto.createHash(\"MD5\").update(sh).digest(\"hex\");\n }\n\n protected handleAuthorizationInterrupts(err: Auth0Interrupt) {\n throw err;\n }\n\n protected validateToken(tokenResponse?: TokenResponse) {\n const store = asyncLocalStorage.getStore();\n if (!store) {\n throw new Error(\n \"The tool must be wrapped with the FederationConnectionAuthorizer.\"\n );\n }\n\n const { scopes, connection } = store;\n\n if (!tokenResponse) {\n throw new FederatedConnectionInterrupt(\n `Authorization required to access the Federated Connection: ${this.params.connection}`,\n connection,\n scopes,\n scopes\n );\n }\n\n const currentScopes = (tokenResponse.scope ?? \"\").split(\" \");\n const missingScopes = scopes.filter((s) => !currentScopes.includes(s));\n store.currentScopes = currentScopes;\n\n if (missingScopes.length > 0) {\n throw new FederatedConnectionInterrupt(\n `Authorization required to access the Federated Connection: ${this.params.connection}. Missing scopes: ${missingScopes.join(\", \")}`,\n connection,\n scopes,\n [...currentScopes, ...scopes]\n );\n }\n }\n\n private async getAccessTokenImpl(\n ...toolContext: ToolExecuteArgs\n ): Promise<TokenResponse | undefined> {\n const store = asyncLocalStorage.getStore();\n if (!store) {\n throw new Error(\n \"The tool must be wrapped with the FederationConnectionAuthorizer.\"\n );\n }\n\n const { connection } = store;\n\n const subjectToken = await this.getRefreshToken(...toolContext);\n\n if (!subjectToken) {\n return;\n }\n\n const exchangeParams = {\n grant_type:\n \"urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token\",\n client_id: this.auth0.clientId,\n client_secret: this.auth0.clientSecret,\n subject_token_type: \"urn:ietf:params:oauth:token-type:refresh_token\",\n subject_token: subjectToken,\n connection: connection,\n requested_token_type:\n \"http://auth0.com/oauth/token-type/federated-connection-access-token\",\n };\n\n const res = await fetch(`https://${this.auth0.domain}/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(exchangeParams),\n });\n\n if (!res.ok) {\n return;\n }\n\n const tokenResponse: TokenResponse = await res.json();\n return tokenResponse;\n }\n\n protected async getAccessToken(\n ...toolContext: ToolExecuteArgs\n ): Promise<TokenSet> {\n let tokenResponse: TokenResponse | undefined;\n if (typeof this.params.refreshToken === \"function\") {\n tokenResponse = await this.getAccessTokenImpl(...toolContext);\n } else {\n tokenResponse = await resolveParameter(\n this.params.accessToken!,\n toolContext\n );\n }\n this.validateToken(tokenResponse);\n return tokenSetFromTokenResponse(tokenResponse!);\n }\n\n protected async getRefreshToken(...toolContext: ToolExecuteArgs) {\n return await resolveParameter(this.params.refreshToken, toolContext);\n }\n\n /**\n *\n * Wraps the execute method of an AI tool to handle Federated Connections authorization.\n *\n * @param getContext - A function that returns the context of the tool execution.\n * @param execute - The tool execute method.\n * @returns The wrapped execute method.\n */\n protect(\n getContext: ContextGetter<ToolExecuteArgs>,\n execute: (...args: ToolExecuteArgs) => any\n ): (...args: ToolExecuteArgs) => any {\n return async (...args: ToolExecuteArgs) => {\n const context = getContext(...args);\n const asyncStore: AsyncStorageValue = {\n context: context,\n scopes: this.params.scopes,\n connection: this.params.connection,\n };\n\n if (asyncLocalStorage.getStore()) {\n throw new Error(\n \"Cannot nest tool calls that require federated connection authorization.\"\n );\n }\n\n return asyncLocalStorage.run(asyncStore, async () => {\n const credentialsNS = nsFromContext(\n this.params.credentialsContext,\n context\n );\n try {\n let credentials = await this.credentialsStore.get(\n credentialsNS,\n \"credential\"\n );\n if (!credentials) {\n credentials = await this.getAccessToken(...args);\n await this.credentialsStore.put(\n credentialsNS,\n \"credential\",\n credentials\n );\n }\n asyncStore.credentials = credentials;\n return await execute(...args);\n } catch (err) {\n if (err instanceof FederatedConnectionError) {\n const interrupt = new FederatedConnectionInterrupt(\n err.message,\n asyncStore.connection,\n asyncStore.scopes,\n asyncStore.scopes\n );\n return this.handleAuthorizationInterrupts(interrupt);\n }\n if (err instanceof Auth0Interrupt) {\n return this.handleAuthorizationInterrupts(err);\n }\n throw err;\n }\n });\n };\n }\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { TokenResponse } from "../../credentials";
|
|
2
|
+
import { AuthorizerToolParameter } from "../../parameters";
|
|
3
|
+
import { Store } from "../../stores";
|
|
4
|
+
import { AuthContext } from "../context";
|
|
5
|
+
export type FederatedConnectionAuthorizerParams<ToolExecuteArgs extends any[]> = {
|
|
6
|
+
/**
|
|
7
|
+
* The Auth0 refresh token to exchange for an Federated Connection access token.
|
|
8
|
+
*/
|
|
9
|
+
refreshToken?: AuthorizerToolParameter<ToolExecuteArgs, string | undefined>;
|
|
10
|
+
/**
|
|
11
|
+
* The Federated connection access token if available in the tool context.
|
|
12
|
+
*/
|
|
13
|
+
accessToken?: AuthorizerToolParameter<ToolExecuteArgs, TokenResponse | undefined>;
|
|
14
|
+
/**
|
|
15
|
+
* The scopes required in the access token of the federated connection provider.
|
|
16
|
+
*/
|
|
17
|
+
scopes: string[];
|
|
18
|
+
/**
|
|
19
|
+
* The connection name of the federated connection provider.
|
|
20
|
+
*/
|
|
21
|
+
connection: string;
|
|
22
|
+
/**
|
|
23
|
+
* AuthContext defines the scope of credential sharing:
|
|
24
|
+
* - "tool-call": Credentials are valid only for a single invocation of the tool.
|
|
25
|
+
* - "tool": Credentials are shared across multiple calls to the same tool within the same thread.
|
|
26
|
+
* - "thread": Credentials are shared across all tools using the same authorizer within the current thread.
|
|
27
|
+
* - "agent": Credentials are shared globally across all threads and tools in the agent.
|
|
28
|
+
*
|
|
29
|
+
* @default "thread"
|
|
30
|
+
*/
|
|
31
|
+
credentialsContext?: AuthContext;
|
|
32
|
+
/**
|
|
33
|
+
* An store used to store the authorization credentials according
|
|
34
|
+
* to the `credentialsContext` scope.
|
|
35
|
+
*/
|
|
36
|
+
store: Store;
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=FederatedConnectionAuthorizerParams.d.ts.map
|