@base44-preview/sdk 0.8.51-pr.294.a7ae332 → 0.8.52-pr.295.572d9e6
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.
|
@@ -61,7 +61,10 @@ export function createFunctionsModule(axios, appId, config) {
|
|
|
61
61
|
formData = data;
|
|
62
62
|
contentType = "application/json";
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
// Return only the response fields: the axios response also carries the
|
|
65
|
+
// request config and raw request, both of which hold the auth headers.
|
|
66
|
+
const { data: responseData, status, statusText, headers } = await axios.post(`/apps/${appId}/functions/${functionName}`, formData || data, { headers: { "Content-Type": contentType } });
|
|
67
|
+
return { data: responseData, status, statusText, headers };
|
|
65
68
|
},
|
|
66
69
|
// Fetch a backend function endpoint directly.
|
|
67
70
|
async fetch(path, init = {}) {
|
|
@@ -59,7 +59,7 @@ export interface FunctionsModule {
|
|
|
59
59
|
*
|
|
60
60
|
* @param functionName - The name of the function to invoke.
|
|
61
61
|
* @param data - An object containing named parameters for the function.
|
|
62
|
-
* @returns Promise resolving to the function's response. The `data` property contains the data returned by the function, if there is any.
|
|
62
|
+
* @returns Promise resolving to the function's response, with `data`, `status`, `statusText`, and `headers` properties. The `data` property contains the data returned by the function, if there is any. The request config isn't included, since it holds the auth headers.
|
|
63
63
|
*
|
|
64
64
|
* @example
|
|
65
65
|
* ```typescript
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import axios from "axios";
|
|
1
|
+
import axios, { AxiosHeaders } from "axios";
|
|
2
2
|
import { isInIFrame } from "./common.js";
|
|
3
3
|
import { v4 as uuidv4 } from "uuid";
|
|
4
4
|
import { getAnalyticsSessionId } from "../modules/analytics.js";
|
|
@@ -101,6 +101,134 @@ function safeErrorLog(prefix, error) {
|
|
|
101
101
|
console.error(`${prefix} ${error instanceof Error ? error.message : String(error)}`);
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
+
const REDACTED = "[REDACTED]";
|
|
105
|
+
const CREDENTIAL_HEADERS = [
|
|
106
|
+
"Authorization",
|
|
107
|
+
"on-behalf-of",
|
|
108
|
+
"Base44-Service-Authorization",
|
|
109
|
+
// Signed proof that the caller passed the IP allowlist; replayable if logged.
|
|
110
|
+
"Base44-State",
|
|
111
|
+
];
|
|
112
|
+
// Request body fields that hold secrets (see the bodies built in modules/auth.ts).
|
|
113
|
+
const SECRET_BODY_KEYS = new Set([
|
|
114
|
+
"password",
|
|
115
|
+
"current_password",
|
|
116
|
+
"new_password",
|
|
117
|
+
"reset_token",
|
|
118
|
+
"turnstile_token",
|
|
119
|
+
"otp_code",
|
|
120
|
+
"access_token",
|
|
121
|
+
"refresh_token",
|
|
122
|
+
"token",
|
|
123
|
+
]);
|
|
124
|
+
/**
|
|
125
|
+
* Returns a copy of request headers with credential values replaced.
|
|
126
|
+
*
|
|
127
|
+
* @param headers - The request's headers, left unchanged
|
|
128
|
+
* @returns A new AxiosHeaders instance
|
|
129
|
+
* @internal
|
|
130
|
+
*/
|
|
131
|
+
function redactHeaders(headers) {
|
|
132
|
+
// Not `AxiosHeaders.from`, which returns an AxiosHeaders argument as-is.
|
|
133
|
+
const copy = new AxiosHeaders(headers);
|
|
134
|
+
for (const name of CREDENTIAL_HEADERS) {
|
|
135
|
+
if (copy.has(name)) {
|
|
136
|
+
copy.set(name, REDACTED);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return copy;
|
|
140
|
+
}
|
|
141
|
+
function isPlainObject(value) {
|
|
142
|
+
if (value === null || typeof value !== "object")
|
|
143
|
+
return false;
|
|
144
|
+
const proto = Object.getPrototypeOf(value);
|
|
145
|
+
return proto === Object.prototype || proto === null;
|
|
146
|
+
}
|
|
147
|
+
function redactSecretKeys(value, seen) {
|
|
148
|
+
if (Array.isArray(value)) {
|
|
149
|
+
if (seen.has(value))
|
|
150
|
+
return value;
|
|
151
|
+
seen.add(value);
|
|
152
|
+
return value.map((item) => redactSecretKeys(item, seen));
|
|
153
|
+
}
|
|
154
|
+
if (!isPlainObject(value) || seen.has(value))
|
|
155
|
+
return value;
|
|
156
|
+
seen.add(value);
|
|
157
|
+
const copy = {};
|
|
158
|
+
for (const [key, item] of Object.entries(value)) {
|
|
159
|
+
copy[key] = SECRET_BODY_KEYS.has(key.toLowerCase())
|
|
160
|
+
? REDACTED
|
|
161
|
+
: redactSecretKeys(item, seen);
|
|
162
|
+
}
|
|
163
|
+
return copy;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Returns a copy of a request body with secret fields replaced.
|
|
167
|
+
*
|
|
168
|
+
* Handles both a serialized JSON string (the usual case by the time a request
|
|
169
|
+
* has been sent) and a plain object. Other bodies, such as `FormData`, are
|
|
170
|
+
* returned unchanged.
|
|
171
|
+
*
|
|
172
|
+
* @param data - The request body, left unchanged
|
|
173
|
+
* @returns The redacted body
|
|
174
|
+
* @internal
|
|
175
|
+
*/
|
|
176
|
+
function redactBody(data) {
|
|
177
|
+
if (typeof data === "string") {
|
|
178
|
+
let parsed;
|
|
179
|
+
try {
|
|
180
|
+
parsed = JSON.parse(data);
|
|
181
|
+
}
|
|
182
|
+
catch (_a) {
|
|
183
|
+
return data;
|
|
184
|
+
}
|
|
185
|
+
return typeof parsed === "object" && parsed !== null
|
|
186
|
+
? JSON.stringify(redactSecretKeys(parsed, new WeakSet()))
|
|
187
|
+
: data;
|
|
188
|
+
}
|
|
189
|
+
return redactSecretKeys(data, new WeakSet());
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Removes credentials from a failed request's error before app code sees it.
|
|
193
|
+
*
|
|
194
|
+
* An axios error carries its request config and the raw Node request, so
|
|
195
|
+
* logging it (for example `console.log(error)`) printed the bearer token.
|
|
196
|
+
* The config is replaced with a redacted copy, so the object axios sent is not
|
|
197
|
+
* changed, and the raw request is removed.
|
|
198
|
+
*
|
|
199
|
+
* Never throws: a failure here must not replace the original error.
|
|
200
|
+
*
|
|
201
|
+
* @param error - The rejection value to scrub
|
|
202
|
+
* @internal
|
|
203
|
+
*/
|
|
204
|
+
function sanitizeError(error) {
|
|
205
|
+
var _a;
|
|
206
|
+
if (!error || typeof error !== "object")
|
|
207
|
+
return;
|
|
208
|
+
try {
|
|
209
|
+
const target = error;
|
|
210
|
+
const config = target.config;
|
|
211
|
+
if (config && typeof config === "object") {
|
|
212
|
+
const redactedConfig = {
|
|
213
|
+
...config,
|
|
214
|
+
headers: redactHeaders(config.headers),
|
|
215
|
+
data: redactBody(config.data),
|
|
216
|
+
};
|
|
217
|
+
target.config = redactedConfig;
|
|
218
|
+
if (((_a = target.response) === null || _a === void 0 ? void 0 : _a.config) === config) {
|
|
219
|
+
target.response.config = redactedConfig;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// The Node request object also holds the raw header block (`_header`).
|
|
223
|
+
delete target.request;
|
|
224
|
+
if (target.response && typeof target.response === "object") {
|
|
225
|
+
delete target.response.request;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch (_b) {
|
|
229
|
+
/* leave the error as it is */
|
|
230
|
+
}
|
|
231
|
+
}
|
|
104
232
|
/**
|
|
105
233
|
* Creates an axios client with default configuration and interceptors.
|
|
106
234
|
*
|
|
@@ -128,6 +256,11 @@ export function createAxiosClient({ baseURL, headers = {}, token, interceptRespo
|
|
|
128
256
|
if (token) {
|
|
129
257
|
client.defaults.headers.common["Authorization"] = `Bearer ${token}`;
|
|
130
258
|
}
|
|
259
|
+
// Registered first so it also covers clients that skip the Base44Error wrapper.
|
|
260
|
+
client.interceptors.response.use(undefined, (error) => {
|
|
261
|
+
sanitizeError(error);
|
|
262
|
+
return Promise.reject(error);
|
|
263
|
+
});
|
|
131
264
|
// Add origin URL in browser environment
|
|
132
265
|
client.interceptors.request.use((config) => {
|
|
133
266
|
// `window.location` is absent on React Native (where `window` still exists),
|