veryfront 0.1.458 → 0.1.460
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/esm/deno.d.ts +1 -24
- package/esm/deno.js +2 -25
- package/esm/src/agent/hosted-chat-preparation.d.ts +37 -1
- package/esm/src/agent/hosted-chat-preparation.d.ts.map +1 -1
- package/esm/src/agent/hosted-chat-preparation.js +46 -0
- package/esm/src/agent/hosted-service-auth.d.ts +64 -0
- package/esm/src/agent/hosted-service-auth.d.ts.map +1 -0
- package/esm/src/agent/hosted-service-auth.js +270 -0
- package/esm/src/agent/index.d.ts +2 -1
- package/esm/src/agent/index.d.ts.map +1 -1
- package/esm/src/agent/index.js +2 -1
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
- package/src/deno.js +2 -25
- package/src/src/agent/hosted-chat-preparation.ts +130 -0
- package/src/src/agent/hosted-service-auth.ts +397 -0
- package/src/src/agent/index.ts +23 -0
- package/src/src/utils/version-constant.ts +1 -1
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { importSPKI, jwtVerify, type KeyLike } from "jose";
|
|
2
|
+
|
|
3
|
+
export type HostedServiceAuthErrorCode =
|
|
4
|
+
| "UNAUTHENTICATED"
|
|
5
|
+
| "FORBIDDEN"
|
|
6
|
+
| "NOT_FOUND"
|
|
7
|
+
| "SERVER_ERROR";
|
|
8
|
+
|
|
9
|
+
export class HostedServiceAuthError extends Error {
|
|
10
|
+
readonly statusCode: number;
|
|
11
|
+
readonly errorCode: HostedServiceAuthErrorCode;
|
|
12
|
+
|
|
13
|
+
constructor(statusCode: number, message: string) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "HostedServiceAuthError";
|
|
16
|
+
this.statusCode = statusCode;
|
|
17
|
+
this.errorCode = statusCode === 401 ? "UNAUTHENTICATED" : "FORBIDDEN";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isHostedServiceAuthError(
|
|
22
|
+
error: unknown,
|
|
23
|
+
): error is HostedServiceAuthError {
|
|
24
|
+
return error instanceof HostedServiceAuthError;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type HostedServiceAuthenticatedRequest = {
|
|
28
|
+
authToken: string;
|
|
29
|
+
userId: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type HostedServiceJwtError = {
|
|
33
|
+
statusCode: number;
|
|
34
|
+
errorCode: HostedServiceAuthErrorCode;
|
|
35
|
+
message: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type HostedServiceJwtResult =
|
|
39
|
+
| { success: true; userId: string; email: string; token: string }
|
|
40
|
+
| { success: false; error: HostedServiceJwtError };
|
|
41
|
+
|
|
42
|
+
export type HostedServiceProjectAccessError = {
|
|
43
|
+
statusCode: number;
|
|
44
|
+
errorCode: HostedServiceAuthErrorCode;
|
|
45
|
+
message: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type HostedServiceProjectAccessResult =
|
|
49
|
+
| { success: true; projectId: string }
|
|
50
|
+
| { success: false; error: HostedServiceProjectAccessError };
|
|
51
|
+
|
|
52
|
+
export type HostedServiceAuthConfig = {
|
|
53
|
+
OAUTH_PUBLIC_KEY?: string | null;
|
|
54
|
+
NODE_ENV?: string | null;
|
|
55
|
+
VERYFRONT_API_URL: string;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type HostedServiceAuthLogger = {
|
|
59
|
+
debug?: (message: string, metadata?: Record<string, unknown>) => void;
|
|
60
|
+
error?: (message: string, metadata?: Record<string, unknown>) => void;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type HostedServiceAuthTrace = <TResult>(
|
|
64
|
+
operationName: string,
|
|
65
|
+
operation: () => Promise<TResult>,
|
|
66
|
+
) => Promise<TResult>;
|
|
67
|
+
|
|
68
|
+
export type HostedServiceAuthFetch = (
|
|
69
|
+
input: string | URL | Request,
|
|
70
|
+
init?: RequestInit,
|
|
71
|
+
) => Promise<Response>;
|
|
72
|
+
|
|
73
|
+
export type HostedServiceAuthOptions = {
|
|
74
|
+
getConfig: () => HostedServiceAuthConfig;
|
|
75
|
+
logger?: HostedServiceAuthLogger;
|
|
76
|
+
trace?: HostedServiceAuthTrace;
|
|
77
|
+
fetch?: HostedServiceAuthFetch;
|
|
78
|
+
projectAccessTimeoutMs?: number;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type HostedServiceAuth = {
|
|
82
|
+
authenticateRequest: (
|
|
83
|
+
request: Request,
|
|
84
|
+
) => Promise<HostedServiceAuthenticatedRequest | Response>;
|
|
85
|
+
getTokenFromRequest: typeof getHostedServiceTokenFromRequest;
|
|
86
|
+
verifyJwt: (token: string) => Promise<HostedServiceJwtResult>;
|
|
87
|
+
verifyProjectAccess: (
|
|
88
|
+
projectId: string,
|
|
89
|
+
token: string,
|
|
90
|
+
) => Promise<HostedServiceProjectAccessResult>;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
let cachedPublicKeyInput: string | undefined;
|
|
94
|
+
let cachedPublicKeyPromise: Promise<KeyLike> | undefined;
|
|
95
|
+
|
|
96
|
+
function defaultTrace<TResult>(
|
|
97
|
+
_operationName: string,
|
|
98
|
+
operation: () => Promise<TResult>,
|
|
99
|
+
): Promise<TResult> {
|
|
100
|
+
return operation();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getFetch(options: HostedServiceAuthOptions): HostedServiceAuthFetch {
|
|
104
|
+
return options.fetch ?? fetch;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getProjectAccessTimeoutMs(options: HostedServiceAuthOptions): number {
|
|
108
|
+
return options.projectAccessTimeoutMs ?? 15_000;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getPublicKey(publicKeyInput: string): Promise<KeyLike> {
|
|
112
|
+
if (cachedPublicKeyInput !== publicKeyInput || !cachedPublicKeyPromise) {
|
|
113
|
+
cachedPublicKeyInput = publicKeyInput;
|
|
114
|
+
cachedPublicKeyPromise = importSPKI(publicKeyInput, "RS256");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return cachedPublicKeyPromise;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function getHostedServiceTokenFromRequest(request: Request): string | null {
|
|
121
|
+
const cookies = request.headers.get("cookie") || "";
|
|
122
|
+
const cookieMatch = cookies.match(/(?:^|;\s*)authToken=([^;]+)/);
|
|
123
|
+
if (cookieMatch?.[1]) return cookieMatch[1];
|
|
124
|
+
|
|
125
|
+
const authHeader = request.headers.get("authorization");
|
|
126
|
+
if (authHeader?.startsWith("Bearer ")) return authHeader.slice(7);
|
|
127
|
+
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function makeUnauthenticatedError(message: string): HostedServiceJwtError {
|
|
132
|
+
return {
|
|
133
|
+
statusCode: 401,
|
|
134
|
+
errorCode: "UNAUTHENTICATED",
|
|
135
|
+
message,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function decodeBase64Url(input: string): string {
|
|
140
|
+
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
141
|
+
const paddingLength = (4 - (normalized.length % 4)) % 4;
|
|
142
|
+
const padded = `${normalized}${"=".repeat(paddingLength)}`;
|
|
143
|
+
|
|
144
|
+
if (typeof atob !== "function") {
|
|
145
|
+
throw new Error("Base64URL decoding is not available in this runtime");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const binary = atob(padded);
|
|
149
|
+
const bytes = new Uint8Array(binary.length);
|
|
150
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
151
|
+
bytes[index] = binary.charCodeAt(index);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return new TextDecoder().decode(bytes);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
158
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function parseJsonObject(json: string): Record<string, unknown> | null {
|
|
162
|
+
const parsed: unknown = JSON.parse(json);
|
|
163
|
+
return isRecord(parsed) ? parsed : null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function decodeHostedServiceJwtWithoutVerify(
|
|
167
|
+
token: string,
|
|
168
|
+
): HostedServiceJwtResult {
|
|
169
|
+
try {
|
|
170
|
+
const parts = token.split(".");
|
|
171
|
+
if (parts.length !== 3) {
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
error: makeUnauthenticatedError("Invalid token format"),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const payloadPart = parts[1];
|
|
179
|
+
if (!payloadPart) {
|
|
180
|
+
return {
|
|
181
|
+
success: false,
|
|
182
|
+
error: makeUnauthenticatedError("Invalid token format"),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const payload = parseJsonObject(decodeBase64Url(payloadPart));
|
|
187
|
+
if (!payload) {
|
|
188
|
+
return {
|
|
189
|
+
success: false,
|
|
190
|
+
error: makeUnauthenticatedError("Invalid token"),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (typeof payload.exp === "number" && payload.exp * 1000 < Date.now()) {
|
|
195
|
+
return {
|
|
196
|
+
success: false,
|
|
197
|
+
error: makeUnauthenticatedError("Token expired"),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const userId = typeof payload.userId === "string" ? payload.userId : null;
|
|
202
|
+
if (!userId) {
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
error: makeUnauthenticatedError("Invalid token: missing userId"),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
success: true,
|
|
211
|
+
userId,
|
|
212
|
+
email: typeof payload.email === "string" ? payload.email : "",
|
|
213
|
+
token,
|
|
214
|
+
};
|
|
215
|
+
} catch {
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
error: makeUnauthenticatedError("Invalid token"),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function createHostedServiceAuth(
|
|
224
|
+
options: HostedServiceAuthOptions,
|
|
225
|
+
): HostedServiceAuth {
|
|
226
|
+
const trace = options.trace ?? defaultTrace;
|
|
227
|
+
|
|
228
|
+
async function verifyJwt(token: string): Promise<HostedServiceJwtResult> {
|
|
229
|
+
return await trace("auth.verifyJwt", async () => {
|
|
230
|
+
if (!token) {
|
|
231
|
+
return {
|
|
232
|
+
success: false,
|
|
233
|
+
error: makeUnauthenticatedError("Authentication token required"),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const config = options.getConfig();
|
|
238
|
+
|
|
239
|
+
if (!config.OAUTH_PUBLIC_KEY) {
|
|
240
|
+
if (config.NODE_ENV === "production") {
|
|
241
|
+
return {
|
|
242
|
+
success: false,
|
|
243
|
+
error: {
|
|
244
|
+
statusCode: 500,
|
|
245
|
+
errorCode: "SERVER_ERROR",
|
|
246
|
+
message: "JWT public key not configured",
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return decodeHostedServiceJwtWithoutVerify(token);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
const publicKey = await getPublicKey(config.OAUTH_PUBLIC_KEY);
|
|
255
|
+
const { payload } = await jwtVerify(token, publicKey, {
|
|
256
|
+
algorithms: ["RS256"],
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const userId = typeof payload.userId === "string" ? payload.userId : null;
|
|
260
|
+
if (!userId) {
|
|
261
|
+
return {
|
|
262
|
+
success: false,
|
|
263
|
+
error: makeUnauthenticatedError("Invalid token: missing userId"),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
success: true,
|
|
269
|
+
userId,
|
|
270
|
+
email: typeof payload.email === "string" ? payload.email : "",
|
|
271
|
+
token,
|
|
272
|
+
};
|
|
273
|
+
} catch (error) {
|
|
274
|
+
options.logger?.debug?.("JWT verification failed", {
|
|
275
|
+
error: error instanceof Error ? error.message : String(error),
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
279
|
+
if (errorMessage.includes("expired")) {
|
|
280
|
+
return {
|
|
281
|
+
success: false,
|
|
282
|
+
error: makeUnauthenticatedError("Token expired"),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
success: false,
|
|
288
|
+
error: makeUnauthenticatedError("Invalid token"),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function authenticateRequest(
|
|
295
|
+
request: Request,
|
|
296
|
+
): Promise<HostedServiceAuthenticatedRequest | Response> {
|
|
297
|
+
const token = getHostedServiceTokenFromRequest(request);
|
|
298
|
+
if (!token) {
|
|
299
|
+
return Response.json({ errorCode: "UNAUTHENTICATED" }, { status: 401 });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const auth = await verifyJwt(token);
|
|
303
|
+
if (!auth.success) {
|
|
304
|
+
return Response.json({ errorCode: auth.error.errorCode }, { status: 401 });
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
authToken: auth.token,
|
|
309
|
+
userId: auth.userId,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function verifyProjectAccess(
|
|
314
|
+
projectId: string,
|
|
315
|
+
token: string,
|
|
316
|
+
): Promise<HostedServiceProjectAccessResult> {
|
|
317
|
+
return await trace("auth.verifyProjectAccess", async () => {
|
|
318
|
+
const config = options.getConfig();
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
const apiUrl = new URL(config.VERYFRONT_API_URL);
|
|
322
|
+
const restUrl = new URL(`/projects/${projectId}`, apiUrl.origin);
|
|
323
|
+
|
|
324
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
325
|
+
if (token) {
|
|
326
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const response = await getFetch(options)(restUrl.toString(), {
|
|
330
|
+
method: "GET",
|
|
331
|
+
headers,
|
|
332
|
+
signal: AbortSignal.timeout(getProjectAccessTimeoutMs(options)),
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
if (response.status === 404) {
|
|
336
|
+
return {
|
|
337
|
+
success: false,
|
|
338
|
+
error: {
|
|
339
|
+
statusCode: 404,
|
|
340
|
+
errorCode: "NOT_FOUND",
|
|
341
|
+
message: "Project not found",
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (response.status === 401 || response.status === 403) {
|
|
347
|
+
return {
|
|
348
|
+
success: false,
|
|
349
|
+
error: {
|
|
350
|
+
statusCode: 403,
|
|
351
|
+
errorCode: "FORBIDDEN",
|
|
352
|
+
message: "No access to project",
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (!response.ok) {
|
|
358
|
+
const errorText = await response.text();
|
|
359
|
+
options.logger?.error?.("Project access check failed", {
|
|
360
|
+
error: errorText,
|
|
361
|
+
projectId,
|
|
362
|
+
});
|
|
363
|
+
return {
|
|
364
|
+
success: false,
|
|
365
|
+
error: {
|
|
366
|
+
statusCode: 403,
|
|
367
|
+
errorCode: "FORBIDDEN",
|
|
368
|
+
message: "No access to project",
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
success: true,
|
|
375
|
+
projectId,
|
|
376
|
+
};
|
|
377
|
+
} catch (error) {
|
|
378
|
+
options.logger?.error?.("Project access check failed", { error, projectId });
|
|
379
|
+
return {
|
|
380
|
+
success: false,
|
|
381
|
+
error: {
|
|
382
|
+
statusCode: 403,
|
|
383
|
+
errorCode: "FORBIDDEN",
|
|
384
|
+
message: "No access to project",
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
authenticateRequest,
|
|
393
|
+
getTokenFromRequest: getHostedServiceTokenFromRequest,
|
|
394
|
+
verifyJwt,
|
|
395
|
+
verifyProjectAccess,
|
|
396
|
+
};
|
|
397
|
+
}
|
package/src/src/agent/index.ts
CHANGED
|
@@ -681,6 +681,9 @@ export {
|
|
|
681
681
|
type PrepareAgentRuntimeMessagesFromUiMessagesOptions,
|
|
682
682
|
} from "./runtime-message-preparation.js";
|
|
683
683
|
export {
|
|
684
|
+
type HostedChatExecutionPreparationInput,
|
|
685
|
+
type HostedChatExecutionPreparationResult,
|
|
686
|
+
type HostedChatExecutionPreparationRootRunOptions,
|
|
684
687
|
type HostedChatRuntimeCreationPreparationInput,
|
|
685
688
|
type HostedChatRuntimeCreationPreparationResult,
|
|
686
689
|
type HostedChatRuntimeInstructionsInput,
|
|
@@ -688,6 +691,7 @@ export {
|
|
|
688
691
|
type HostedChatRuntimePreparationSteering,
|
|
689
692
|
type NormalizedHostedChatRequest,
|
|
690
693
|
normalizeParsedHostedChatRequest,
|
|
694
|
+
prepareHostedChatExecution,
|
|
691
695
|
prepareHostedChatRuntimeCreationOptions,
|
|
692
696
|
prepareHostedChatRuntimeMessages,
|
|
693
697
|
type PrepareHostedChatRuntimeMessagesOptions,
|
|
@@ -1294,3 +1298,22 @@ export {
|
|
|
1294
1298
|
WaitConflictError,
|
|
1295
1299
|
WaitNotPendingError,
|
|
1296
1300
|
} from "./runtime/index.js";
|
|
1301
|
+
|
|
1302
|
+
export {
|
|
1303
|
+
createHostedServiceAuth,
|
|
1304
|
+
getHostedServiceTokenFromRequest,
|
|
1305
|
+
type HostedServiceAuth,
|
|
1306
|
+
type HostedServiceAuthConfig,
|
|
1307
|
+
type HostedServiceAuthenticatedRequest,
|
|
1308
|
+
HostedServiceAuthError,
|
|
1309
|
+
type HostedServiceAuthErrorCode,
|
|
1310
|
+
type HostedServiceAuthFetch,
|
|
1311
|
+
type HostedServiceAuthLogger,
|
|
1312
|
+
type HostedServiceAuthOptions,
|
|
1313
|
+
type HostedServiceAuthTrace,
|
|
1314
|
+
type HostedServiceJwtError,
|
|
1315
|
+
type HostedServiceJwtResult,
|
|
1316
|
+
type HostedServiceProjectAccessError,
|
|
1317
|
+
type HostedServiceProjectAccessResult,
|
|
1318
|
+
isHostedServiceAuthError,
|
|
1319
|
+
} from "./hosted-service-auth.js";
|