@base44-preview/sdk 0.8.52-pr.297.c6f2448 → 0.8.52-pr.298.d08e0ba
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 +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/modules/ai-gateway.types.d.ts +3 -87
- package/dist/modules/auth.js +13 -6
- package/dist/modules/auth.types.d.ts +7 -3
- package/dist/modules/mfa.d.ts +52 -0
- package/dist/modules/mfa.js +43 -0
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -18,3 +18,4 @@ export { Actor, type Conn } from "./actor.js";
|
|
|
18
18
|
export type { ConnectorsModule, UserConnectorsModule, ConnectorApiRequest, ConnectorApiResponse, ConnectorApiResponsePhase, } from "./modules/connectors.types.js";
|
|
19
19
|
export type { CustomIntegrationsModule, CustomIntegrationCallParams, CustomIntegrationCallResponse, } from "./modules/custom-integrations.types.js";
|
|
20
20
|
export type { GetAccessTokenOptions, SaveAccessTokenOptions, RemoveAccessTokenOptions, GetLoginUrlOptions, } from "./utils/auth-utils.types.js";
|
|
21
|
+
export type { MfaChallenge, MfaModule, MfaProof, LoginResult, MfaEnrollmentResult } from "./modules/mfa.js";
|
|
@@ -51,9 +51,9 @@ export interface AiGatewayModuleConfig {
|
|
|
51
51
|
* against the provider directly, no separate account, API key, or billing
|
|
52
52
|
* setup with the underlying model provider required.
|
|
53
53
|
*
|
|
54
|
-
* By default, `connection()` uses the OpenAI-compatible provider
|
|
55
|
-
*
|
|
56
|
-
*
|
|
54
|
+
* By default, `connection()` uses the OpenAI-compatible provider. Pass
|
|
55
|
+
* `{ provider: "typesafe" }` to connect to TypeSafe for structured evaluations
|
|
56
|
+
* with `@ai-sdk/typesafe-ai`.
|
|
57
57
|
*
|
|
58
58
|
* Call `connection()` from a backend function rather than the browser. This
|
|
59
59
|
* keeps your instructions, tools, and business logic server-side, and lets
|
|
@@ -76,26 +76,6 @@ export interface AiGatewayModuleConfig {
|
|
|
76
76
|
* `'gemini_3_1_flash_image'` or `'gpt_image_2'`. For request options, the
|
|
77
77
|
* full model list, and limits, see [Generate images with the AI Gateway](/developers/references/sdk/getting-started/ai-gateway-images).
|
|
78
78
|
*
|
|
79
|
-
* ## Videos
|
|
80
|
-
*
|
|
81
|
-
* The gateway serves OpenAI's asynchronous video endpoints through the
|
|
82
|
-
* `openai` SDK's `videos` client. `videos.create()` starts a job and returns
|
|
83
|
-
* its `id` right away; call `videos.retrieve(id)` until `status` is
|
|
84
|
-
* `completed` or `failed`. A completed job exposes the stored video at `url`.
|
|
85
|
-
* A backend function is a bounded HTTP call, so don't poll inside one: return
|
|
86
|
-
* the `id`, then retrieve it from a later call or scheduled run. Video
|
|
87
|
-
* requests need a specific model, such as `'veo_3_1_fast'` or
|
|
88
|
-
* `'seedance_2_fast'`; there's no `'automatic'` option.
|
|
89
|
-
*
|
|
90
|
-
* ## Structured evaluations
|
|
91
|
-
*
|
|
92
|
-
* The `typesafe` provider runs the `jev` evaluation model through
|
|
93
|
-
* `@ai-sdk/typesafe-ai` and the `ai` package's `experimental_evaluate`. Give
|
|
94
|
-
* it some app state and a set of questions, each a `choice`, `score`, or
|
|
95
|
-
* `boolean`, and it returns each answer with its probabilities. Use it
|
|
96
|
-
* to classify, score, or route records, then apply your own thresholds and
|
|
97
|
-
* actions in code.
|
|
98
|
-
*
|
|
99
79
|
* ## Authentication Modes
|
|
100
80
|
*
|
|
101
81
|
* There's no permission difference between modes. Both just determine which
|
|
@@ -207,70 +187,6 @@ export interface AiGatewayModule {
|
|
|
207
187
|
* const file = new File([image.uint8Array], "lighthouse.png", { type: image.mediaType });
|
|
208
188
|
* const { file_url } = await base44.integrations.Core.UploadFile({ file });
|
|
209
189
|
* ```
|
|
210
|
-
*
|
|
211
|
-
* @example
|
|
212
|
-
* ```typescript
|
|
213
|
-
* // Start a video job, then check on it from a later call
|
|
214
|
-
* import { createClientFromRequest } from "@base44/sdk";
|
|
215
|
-
* import OpenAI from "openai";
|
|
216
|
-
*
|
|
217
|
-
* // Runs inside a backend function
|
|
218
|
-
* const base44 = createClientFromRequest(request);
|
|
219
|
-
* const { baseURL, token, headers } = base44.aiGateway.connection();
|
|
220
|
-
* const { videos } = new OpenAI({ baseURL, apiKey: token, defaultHeaders: headers });
|
|
221
|
-
*
|
|
222
|
-
* const { videoId } = await request.json();
|
|
223
|
-
* if (!videoId) {
|
|
224
|
-
* // Avoid automatic replays of billed requests
|
|
225
|
-
* const job = await videos.create(
|
|
226
|
-
* { model: "veo_3_1_fast", prompt: "A paper boat drifting down a rainy street", seconds: "4" },
|
|
227
|
-
* { maxRetries: 0 },
|
|
228
|
-
* );
|
|
229
|
-
* return Response.json({ videoId: job.id, status: job.status }, { status: 202 });
|
|
230
|
-
* }
|
|
231
|
-
*
|
|
232
|
-
* const video = await videos.retrieve(videoId);
|
|
233
|
-
* // `queued` and `in_progress` mean unfinished; `url` is set once `completed`
|
|
234
|
-
* return Response.json(video);
|
|
235
|
-
* ```
|
|
236
|
-
*
|
|
237
|
-
* @example
|
|
238
|
-
* ```typescript
|
|
239
|
-
* // Route a support ticket with a structured evaluation
|
|
240
|
-
* import { createClientFromRequest } from "@base44/sdk";
|
|
241
|
-
* import { experimental_evaluate as evaluate } from "ai";
|
|
242
|
-
* import { createTypeSafeAi } from "@ai-sdk/typesafe-ai";
|
|
243
|
-
*
|
|
244
|
-
* // Runs inside a backend function
|
|
245
|
-
* const base44 = createClientFromRequest(request);
|
|
246
|
-
* const { baseURL, token, headers } = base44.aiGateway.connection({ provider: "typesafe" });
|
|
247
|
-
* const typesafe = createTypeSafeAi({ baseURL, apiKey: token, headers });
|
|
248
|
-
*
|
|
249
|
-
* const { answers } = await evaluate({
|
|
250
|
-
* model: typesafe.evaluationModel("jev"),
|
|
251
|
-
* maxRetries: 0,
|
|
252
|
-
* state: { message: "Please refund today's duplicate charge." },
|
|
253
|
-
* questions: {
|
|
254
|
-
* department: {
|
|
255
|
-
* type: "choice",
|
|
256
|
-
* instructions: "Which team should handle this?",
|
|
257
|
-
* criteria: { billing: "Payments and refunds", support: "Other requests" },
|
|
258
|
-
* },
|
|
259
|
-
* urgency: {
|
|
260
|
-
* type: "score",
|
|
261
|
-
* instructions: "How urgent is this?",
|
|
262
|
-
* criteria: ["No deadline", "Requested today", "Immediate emergency"],
|
|
263
|
-
* },
|
|
264
|
-
* refund: { type: "boolean", instructions: "Is a refund explicitly requested?" },
|
|
265
|
-
* },
|
|
266
|
-
* });
|
|
267
|
-
*
|
|
268
|
-
* // answers.department → { type: "choice", choice: "billing", probabilities: { billing: 0.97, support: 0.03 } }
|
|
269
|
-
* // answers.urgency → { type: "score", score: 1.2, probabilities: { 0: 0.1, 1: 0.6, 2: 0.3 } }
|
|
270
|
-
* // answers.refund → { type: "boolean", probability: 0.99 }
|
|
271
|
-
* // Pick your own thresholds and act on them in code
|
|
272
|
-
* const autoRefund = answers.department.choice === "billing" && answers.refund.probability > 0.9;
|
|
273
|
-
* ```
|
|
274
190
|
*/
|
|
275
191
|
connection(options?: AiGatewayConnectionOptions): AiGatewayConnection;
|
|
276
192
|
}
|
package/dist/modules/auth.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createMfaModule, prepareMfaPkce } from "./mfa.js";
|
|
1
2
|
import { resetAnalyticsSessionContext } from "./analytics.js";
|
|
2
3
|
function isInsideIframe() {
|
|
3
4
|
if (typeof window === "undefined")
|
|
@@ -19,9 +20,9 @@ function loginViaPopup(url, redirectUrl, expectedOrigin) {
|
|
|
19
20
|
const height = 600;
|
|
20
21
|
const left = Math.round(window.screenX + (window.outerWidth - width) / 2);
|
|
21
22
|
const top = Math.round(window.screenY + (window.outerHeight - height) / 2);
|
|
22
|
-
const popup = window.open(
|
|
23
|
+
const popup = window.open("about:blank", "base44_auth", `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`);
|
|
23
24
|
if (!popup) {
|
|
24
|
-
return;
|
|
25
|
+
return Promise.resolve();
|
|
25
26
|
}
|
|
26
27
|
const cleanup = () => {
|
|
27
28
|
window.removeEventListener("message", onMessage);
|
|
@@ -52,6 +53,7 @@ function loginViaPopup(url, redirectUrl, expectedOrigin) {
|
|
|
52
53
|
cleanup();
|
|
53
54
|
}, 500);
|
|
54
55
|
window.addEventListener("message", onMessage);
|
|
56
|
+
return url.then(value => { popup.location.href = value; }, error => { cleanup(); throw error; });
|
|
55
57
|
}
|
|
56
58
|
/**
|
|
57
59
|
* Creates the auth module for the Base44 SDK.
|
|
@@ -82,7 +84,8 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
82
84
|
// to the identity transitions below (`setToken`, `logout`) instead of to the
|
|
83
85
|
// header a caller may have set on the instance directly.
|
|
84
86
|
let hasAccessToken = Boolean(options.token);
|
|
85
|
-
|
|
87
|
+
const module = {
|
|
88
|
+
mfa: createMfaModule(axios, appId, token => module.setToken(token)),
|
|
86
89
|
hasToken() {
|
|
87
90
|
return hasAccessToken;
|
|
88
91
|
},
|
|
@@ -119,9 +122,10 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
119
122
|
window.location.href = loginUrl;
|
|
120
123
|
},
|
|
121
124
|
// Redirects the user to a provider's login page
|
|
122
|
-
loginWithProvider(provider, fromUrl = "/") {
|
|
125
|
+
async loginWithProvider(provider, fromUrl = "/") {
|
|
123
126
|
// Build the full redirect URL
|
|
124
127
|
const redirectUrl = new URL(fromUrl, window.location.origin).toString();
|
|
128
|
+
const mfaPkce = provider === "sso" ? Promise.resolve("") : prepareMfaPkce(appId).then(value => `&app_mfa_code_challenge=${value}`);
|
|
125
129
|
const queryParams = `app_id=${appId}&from_url=${encodeURIComponent(redirectUrl)}`;
|
|
126
130
|
// SSO uses a different URL structure with appId in the path
|
|
127
131
|
let authPath;
|
|
@@ -137,11 +141,11 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
137
141
|
// When running inside an iframe, use a popup to avoid OAuth providers
|
|
138
142
|
// blocking iframe navigation.
|
|
139
143
|
if (isInsideIframe()) {
|
|
140
|
-
const popupLoginUrl = `${loginUrl}&popup_origin=${encodeURIComponent(window.location.origin)}
|
|
144
|
+
const popupLoginUrl = mfaPkce.then(pkce => `${loginUrl}${pkce}&popup_origin=${encodeURIComponent(window.location.origin)}`);
|
|
141
145
|
return loginViaPopup(popupLoginUrl, redirectUrl, window.location.origin);
|
|
142
146
|
}
|
|
143
147
|
// Default: full-page redirect
|
|
144
|
-
window.location.href = loginUrl;
|
|
148
|
+
window.location.href = loginUrl + await mfaPkce;
|
|
145
149
|
},
|
|
146
150
|
// Logout the current user
|
|
147
151
|
logout(redirectUrl) {
|
|
@@ -207,6 +211,8 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
207
211
|
password,
|
|
208
212
|
...(turnstileToken && { turnstile_token: turnstileToken }),
|
|
209
213
|
});
|
|
214
|
+
if (response.mfa_required)
|
|
215
|
+
return response;
|
|
210
216
|
const { access_token, user } = response;
|
|
211
217
|
if (access_token) {
|
|
212
218
|
this.setToken(access_token);
|
|
@@ -278,4 +284,5 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
278
284
|
});
|
|
279
285
|
},
|
|
280
286
|
};
|
|
287
|
+
return module;
|
|
281
288
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LoginResult, MfaModule } from "./mfa.js";
|
|
1
2
|
/**
|
|
2
3
|
* An authenticated user.
|
|
3
4
|
*/
|
|
@@ -37,6 +38,7 @@ export interface User {
|
|
|
37
38
|
* Response from login endpoints containing user information and access token.
|
|
38
39
|
*/
|
|
39
40
|
export interface LoginResponse {
|
|
41
|
+
mfa_required?: false;
|
|
40
42
|
/** JWT access token for authentication. */
|
|
41
43
|
access_token: string;
|
|
42
44
|
/** User information. */
|
|
@@ -117,6 +119,8 @@ export interface AuthModuleOptions {
|
|
|
117
119
|
* The auth module is only available in user authentication mode (`base44.auth`).
|
|
118
120
|
*/
|
|
119
121
|
export interface AuthModule {
|
|
122
|
+
/** Multi-factor enrollment and login for this app. */
|
|
123
|
+
mfa: MfaModule;
|
|
120
124
|
/**
|
|
121
125
|
* Gets the current authenticated user's information.
|
|
122
126
|
*
|
|
@@ -219,7 +223,7 @@ export interface AuthModule {
|
|
|
219
223
|
* ```
|
|
220
224
|
*
|
|
221
225
|
*/
|
|
222
|
-
loginWithProvider(provider: string, fromUrl?: string): void
|
|
226
|
+
loginWithProvider(provider: string, fromUrl?: string): Promise<void>;
|
|
223
227
|
/**
|
|
224
228
|
* Logs out the current user.
|
|
225
229
|
*
|
|
@@ -302,7 +306,7 @@ export interface AuthModule {
|
|
|
302
306
|
* );
|
|
303
307
|
* ```
|
|
304
308
|
*/
|
|
305
|
-
loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise<
|
|
309
|
+
loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise<LoginResult>;
|
|
306
310
|
/**
|
|
307
311
|
* Checks if the current user is authenticated.
|
|
308
312
|
*
|
|
@@ -427,7 +431,7 @@ export interface AuthModule {
|
|
|
427
431
|
* );
|
|
428
432
|
* ```
|
|
429
433
|
*/
|
|
430
|
-
verifyOtp(params: VerifyOtpParams): Promise<
|
|
434
|
+
verifyOtp(params: VerifyOtpParams): Promise<LoginResult>;
|
|
431
435
|
/**
|
|
432
436
|
* Resends an OTP code to the user's email address.
|
|
433
437
|
*
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AxiosInstance } from 'axios';
|
|
2
|
+
import type { LoginResponse } from './auth.types.js';
|
|
3
|
+
/** A restricted, short-lived login outcome. It is never an access token. */
|
|
4
|
+
export interface MfaChallenge {
|
|
5
|
+
mfa_required: true;
|
|
6
|
+
next_step: 'enroll' | 'verify';
|
|
7
|
+
mfa_methods: ('totp' | 'sms')[];
|
|
8
|
+
mfa_session_token: string;
|
|
9
|
+
expires_in_seconds: number;
|
|
10
|
+
phone_hint?: string;
|
|
11
|
+
access_token?: never;
|
|
12
|
+
user?: never;
|
|
13
|
+
}
|
|
14
|
+
export type LoginResult = LoginResponse | MfaChallenge;
|
|
15
|
+
export interface MfaEnrollmentResult extends LoginResponse {
|
|
16
|
+
backup_codes?: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface MfaProof {
|
|
19
|
+
code: string;
|
|
20
|
+
method: 'totp' | 'sms';
|
|
21
|
+
remember_device?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** App-scoped MFA. Pass the challenge credential explicitly; do not set it as the SDK token. */
|
|
24
|
+
export interface MfaModule {
|
|
25
|
+
getStatus(): Promise<{
|
|
26
|
+
mfa_enabled: boolean;
|
|
27
|
+
methods: ('totp' | 'sms')[];
|
|
28
|
+
backup_codes_remaining: number;
|
|
29
|
+
policy: 'off' | 'required';
|
|
30
|
+
}>;
|
|
31
|
+
/** Requires an app admin session; resets a user in this app only. */
|
|
32
|
+
resetForUser(appUserId: string): Promise<{
|
|
33
|
+
success: true;
|
|
34
|
+
}>;
|
|
35
|
+
/** Open managed settings with fresh primary authentication and factor verification. */
|
|
36
|
+
openSecurity(returnTo?: string): void;
|
|
37
|
+
setupTotp(challenge: MfaChallenge): Promise<{
|
|
38
|
+
secret: string;
|
|
39
|
+
secret_formatted: string;
|
|
40
|
+
qr_code: string;
|
|
41
|
+
}>;
|
|
42
|
+
sendSms(challenge: MfaChallenge, phoneNumber: string): Promise<unknown>;
|
|
43
|
+
enrollTotp(challenge: MfaChallenge, code: string, rememberDevice?: boolean): Promise<MfaEnrollmentResult>;
|
|
44
|
+
enrollSms(challenge: MfaChallenge, code: string, rememberDevice?: boolean): Promise<MfaEnrollmentResult>;
|
|
45
|
+
verify(challenge: MfaChallenge, proof: MfaProof): Promise<LoginResponse>;
|
|
46
|
+
verifyRecoveryCode(challenge: MfaChallenge, code: string, rememberDevice?: boolean): Promise<LoginResponse>;
|
|
47
|
+
resendSms(challenge: MfaChallenge): Promise<unknown>;
|
|
48
|
+
/** Open Base44's managed MFA flow on this app's origin. */
|
|
49
|
+
continueLogin(challenge: MfaChallenge, returnTo?: string): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
export declare function prepareMfaPkce(appId: string): Promise<string>;
|
|
52
|
+
export declare function createMfaModule(axios: AxiosInstance, appId: string, setToken: (token: string) => void): MfaModule;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export async function prepareMfaPkce(appId) {
|
|
2
|
+
const encode = (value) => btoa(String.fromCharCode(...value)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
3
|
+
const verifier = encode(crypto.getRandomValues(new Uint8Array(32)));
|
|
4
|
+
sessionStorage.setItem(`app-mfa-pkce:${appId}`, verifier);
|
|
5
|
+
return encode(new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier))));
|
|
6
|
+
}
|
|
7
|
+
export function createMfaModule(axios, appId, setToken) {
|
|
8
|
+
const post = async (challenge, path, payload = {}) => {
|
|
9
|
+
const response = await axios.post(`/apps/${appId}/auth/mfa${path}`, payload, {
|
|
10
|
+
headers: { Authorization: `Bearer ${challenge.mfa_session_token}` }, withCredentials: true,
|
|
11
|
+
});
|
|
12
|
+
const token = response.access_token;
|
|
13
|
+
if (token)
|
|
14
|
+
setToken(token);
|
|
15
|
+
return response;
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
getStatus: () => axios.get(`/apps/${appId}/auth/mfa/status`),
|
|
19
|
+
resetForUser: appUserId => axios.post(`/apps/${appId}/auth/mfa/reset`, { app_user_id: appUserId }),
|
|
20
|
+
openSecurity(returnTo = '/') {
|
|
21
|
+
const target = new URL(returnTo, window.location.origin);
|
|
22
|
+
if (target.origin !== window.location.origin)
|
|
23
|
+
throw new Error('MFA must return to this app.');
|
|
24
|
+
window.location.href = `/auth/mfa?${new URLSearchParams({ security: '1', from_url: target.pathname + target.search + target.hash })}`;
|
|
25
|
+
},
|
|
26
|
+
setupTotp: challenge => post(challenge, '/totp/setup'),
|
|
27
|
+
sendSms: (challenge, phoneNumber) => post(challenge, '/sms/send-code', { phone_number: phoneNumber }),
|
|
28
|
+
enrollTotp: (challenge, code, rememberDevice = false) => post(challenge, '/totp/verify', { code, remember_device: rememberDevice }),
|
|
29
|
+
enrollSms: (challenge, code, rememberDevice = false) => post(challenge, '/sms/verify', { code, remember_device: rememberDevice }),
|
|
30
|
+
verify: (challenge, proof) => post(challenge, '/verify', proof),
|
|
31
|
+
verifyRecoveryCode: (challenge, code, rememberDevice = false) => post(challenge, '/verify-backup', { backup_code: code, remember_device: rememberDevice }),
|
|
32
|
+
resendSms: challenge => post(challenge, '/resend'),
|
|
33
|
+
async continueLogin(challenge, returnTo = '/') {
|
|
34
|
+
const target = new URL(returnTo, window.location.origin);
|
|
35
|
+
if (target.origin !== window.location.origin)
|
|
36
|
+
throw new Error('MFA must return to this app.');
|
|
37
|
+
const codeChallenge = await prepareMfaPkce(appId);
|
|
38
|
+
const handoff = await post(challenge, '/handoff', { code_challenge: codeChallenge });
|
|
39
|
+
const query = new URLSearchParams({ app_id: appId, mfa_code: handoff.code, from_url: target.pathname + target.search + target.hash });
|
|
40
|
+
window.location.href = `/auth/mfa?${query}`;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|