@base44-preview/cli 0.0.50-pr.484.ff9acc3 → 0.0.51-pr.480.51cf06a
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/dist/cli/index.js +362 -85
- package/dist/cli/index.js.map +14 -12
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -235098,8 +235098,8 @@ var TOKEN_REFRESH_BUFFER_MS = 60 * 1000;
|
|
|
235098
235098
|
var refreshPromise = null;
|
|
235099
235099
|
async function readAuth() {
|
|
235100
235100
|
try {
|
|
235101
|
-
const
|
|
235102
|
-
const result = AuthDataSchema.safeParse(
|
|
235101
|
+
const authData = await readJsonFile(getAuthFilePath());
|
|
235102
|
+
const result = AuthDataSchema.safeParse(authData);
|
|
235103
235103
|
if (!result.success) {
|
|
235104
235104
|
throw new SchemaValidationError("Invalid authentication data", result.error, getAuthFilePath());
|
|
235105
235105
|
}
|
|
@@ -241591,9 +241591,13 @@ var BackendFunctionToolConfigSchema = exports_external.object({
|
|
|
241591
241591
|
function_name: exports_external.string().min(1),
|
|
241592
241592
|
description: exports_external.string().default("agent backend function")
|
|
241593
241593
|
});
|
|
241594
|
+
var CodeModeToolConfigSchema = exports_external.object({
|
|
241595
|
+
code_mode: exports_external.union([exports_external.boolean(), exports_external.record(exports_external.string(), exports_external.unknown())])
|
|
241596
|
+
});
|
|
241594
241597
|
var ToolConfigSchema = exports_external.union([
|
|
241595
241598
|
EntityToolConfigSchema,
|
|
241596
|
-
BackendFunctionToolConfigSchema
|
|
241599
|
+
BackendFunctionToolConfigSchema,
|
|
241600
|
+
CodeModeToolConfigSchema
|
|
241597
241601
|
]);
|
|
241598
241602
|
var AgentConfigSchema = exports_external.looseObject({
|
|
241599
241603
|
name: exports_external.string().trim().min(1).max(100),
|
|
@@ -242133,6 +242137,16 @@ async function updateSSOConfig(authDir, provider, enable) {
|
|
|
242133
242137
|
await writeAuthConfig(authDir, merged);
|
|
242134
242138
|
return merged;
|
|
242135
242139
|
}
|
|
242140
|
+
|
|
242141
|
+
class MissingSSOFieldsError extends InvalidInputError {
|
|
242142
|
+
missingKeys;
|
|
242143
|
+
provider;
|
|
242144
|
+
constructor(provider, missingKeys) {
|
|
242145
|
+
super(`Missing required fields for ${provider}: ${missingKeys.join(", ")}`);
|
|
242146
|
+
this.provider = provider;
|
|
242147
|
+
this.missingKeys = missingKeys;
|
|
242148
|
+
}
|
|
242149
|
+
}
|
|
242136
242150
|
function buildSSOSecrets(provider, options) {
|
|
242137
242151
|
const schema3 = SSO_PROVIDER_SCHEMAS[provider];
|
|
242138
242152
|
const secrets = {};
|
|
@@ -242168,7 +242182,7 @@ function buildSSOSecrets(provider, options) {
|
|
|
242168
242182
|
missing.push("sso_name" /* Name */);
|
|
242169
242183
|
}
|
|
242170
242184
|
if (missing.length > 0) {
|
|
242171
|
-
throw new
|
|
242185
|
+
throw new MissingSSOFieldsError(provider, missing);
|
|
242172
242186
|
}
|
|
242173
242187
|
return Object.fromEntries(Object.entries(secrets).filter(([, v]) => v.length > 0));
|
|
242174
242188
|
}
|
|
@@ -243538,7 +243552,7 @@ import { join as join9 } from "node:path";
|
|
|
243538
243552
|
// package.json
|
|
243539
243553
|
var package_default = {
|
|
243540
243554
|
name: "base44",
|
|
243541
|
-
version: "0.0.
|
|
243555
|
+
version: "0.0.51",
|
|
243542
243556
|
description: "Base44 CLI - Unified interface for managing Base44 applications",
|
|
243543
243557
|
type: "module",
|
|
243544
243558
|
bin: {
|
|
@@ -243593,6 +243607,7 @@ var package_default = {
|
|
|
243593
243607
|
"@types/multer": "^2.0.0",
|
|
243594
243608
|
"@types/node": "^22.10.5",
|
|
243595
243609
|
"@vercel/detect-agent": "^1.1.0",
|
|
243610
|
+
outdent: "^0.8.0",
|
|
243596
243611
|
chalk: "^5.6.2",
|
|
243597
243612
|
chokidar: "^5.0.0",
|
|
243598
243613
|
commander: "^12.1.0",
|
|
@@ -251218,7 +251233,7 @@ function getSocialLoginCommand() {
|
|
|
251218
251233
|
// src/cli/commands/auth/sso.ts
|
|
251219
251234
|
import { dirname as dirname11, join as join16, resolve as resolve3 } from "node:path";
|
|
251220
251235
|
var SSOConfigFileSchema = exports_external.object({
|
|
251221
|
-
provider: exports_external.enum(
|
|
251236
|
+
provider: exports_external.enum(Object.values(KNOWN_SSO_PROVIDERS)),
|
|
251222
251237
|
clientId: exports_external.string(),
|
|
251223
251238
|
clientSecret: exports_external.string(),
|
|
251224
251239
|
scope: exports_external.string().optional(),
|
|
@@ -251236,10 +251251,7 @@ async function loadSSOConfigFile(filePath) {
|
|
|
251236
251251
|
const raw2 = await readJsonFile(resolved);
|
|
251237
251252
|
const result = SSOConfigFileSchema.safeParse(raw2);
|
|
251238
251253
|
if (!result.success) {
|
|
251239
|
-
|
|
251240
|
-
`);
|
|
251241
|
-
throw new InvalidInputError(`Invalid SSO config file ${filePath}:
|
|
251242
|
-
${issues}`);
|
|
251254
|
+
throw new SchemaValidationError("Invalid SSO config file", result.error, filePath);
|
|
251243
251255
|
}
|
|
251244
251256
|
return result.data;
|
|
251245
251257
|
}
|
|
@@ -251262,6 +251274,32 @@ function mergeFileWithFlags(fileConfig, options) {
|
|
|
251262
251274
|
};
|
|
251263
251275
|
}
|
|
251264
251276
|
var providerNames = Object.keys(KNOWN_SSO_PROVIDERS);
|
|
251277
|
+
var SECRET_KEY_TO_FLAG = {
|
|
251278
|
+
["sso_name" /* Name */]: "--sso-name",
|
|
251279
|
+
["sso_client_id" /* ClientId */]: "--client-id",
|
|
251280
|
+
["sso_client_secret" /* ClientSecret */]: "--client-secret",
|
|
251281
|
+
["sso_scope" /* Scope */]: "--scope",
|
|
251282
|
+
["sso_discovery_url" /* DiscoveryUrl */]: "--discovery-url",
|
|
251283
|
+
["sso_tenant_id" /* TenantId */]: "--tenant-id",
|
|
251284
|
+
["sso_auth_endpoint" /* AuthEndpoint */]: "--auth-endpoint",
|
|
251285
|
+
["sso_token_endpoint" /* TokenEndpoint */]: "--token-endpoint",
|
|
251286
|
+
["sso_userinfo_endpoint" /* UserinfoEndpoint */]: "--userinfo-endpoint",
|
|
251287
|
+
["sso_okta_domain" /* OktaDomain */]: "--okta-domain",
|
|
251288
|
+
["sso_jwks_uri" /* JwksUri */]: "--jwks-uri"
|
|
251289
|
+
};
|
|
251290
|
+
function secretKeyToFlag(key) {
|
|
251291
|
+
return SECRET_KEY_TO_FLAG[key];
|
|
251292
|
+
}
|
|
251293
|
+
function exampleCommand(provider) {
|
|
251294
|
+
let cmd = `base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`;
|
|
251295
|
+
if (provider === KNOWN_SSO_PROVIDERS.microsoft)
|
|
251296
|
+
cmd += " --tenant-id <id>";
|
|
251297
|
+
if (provider === KNOWN_SSO_PROVIDERS.okta)
|
|
251298
|
+
cmd += " --okta-domain <domain>";
|
|
251299
|
+
if (provider === KNOWN_SSO_PROVIDERS.custom)
|
|
251300
|
+
cmd += " --sso-name <name> --auth-endpoint <url> --token-endpoint <url> --userinfo-endpoint <url> --jwks-uri <url>";
|
|
251301
|
+
return cmd;
|
|
251302
|
+
}
|
|
251265
251303
|
function validateProvider(provider) {
|
|
251266
251304
|
if (!provider) {
|
|
251267
251305
|
throw new InvalidInputError("Missing --provider.", {
|
|
@@ -251273,12 +251311,12 @@ function validateProvider(provider) {
|
|
|
251273
251311
|
]
|
|
251274
251312
|
});
|
|
251275
251313
|
}
|
|
251276
|
-
if (!(provider in KNOWN_SSO_PROVIDERS)) {
|
|
251277
|
-
throw new InvalidInputError(`Unknown provider "${provider}". Valid providers: ${providerNames.join(", ")}`);
|
|
251278
|
-
}
|
|
251279
251314
|
return provider;
|
|
251280
251315
|
}
|
|
251281
251316
|
async function ssoEnableAction({ isNonInteractive, runTask: runTask2 }, options) {
|
|
251317
|
+
if (options.file && options.envFile) {
|
|
251318
|
+
throw new InvalidInputError("--file and --env-file cannot be used together. Provide the client secret either inside --file or via --env-file.");
|
|
251319
|
+
}
|
|
251282
251320
|
let merged = options;
|
|
251283
251321
|
if (options.file) {
|
|
251284
251322
|
const fileConfig = await loadSSOConfigFile(options.file);
|
|
@@ -251338,7 +251376,23 @@ async function ssoEnableAction({ isNonInteractive, runTask: runTask2 }, options)
|
|
|
251338
251376
|
jwksUri: merged.jwksUri,
|
|
251339
251377
|
ssoName: merged.ssoName
|
|
251340
251378
|
};
|
|
251341
|
-
|
|
251379
|
+
let secrets;
|
|
251380
|
+
try {
|
|
251381
|
+
secrets = buildSSOSecrets(provider, secretOptions);
|
|
251382
|
+
} catch (error48) {
|
|
251383
|
+
if (error48 instanceof MissingSSOFieldsError) {
|
|
251384
|
+
const flagNames = error48.missingKeys.map(secretKeyToFlag);
|
|
251385
|
+
throw new InvalidInputError(`Missing required fields for ${error48.provider}: ${flagNames.join(", ")}`, {
|
|
251386
|
+
hints: [
|
|
251387
|
+
{
|
|
251388
|
+
message: `Example: ${exampleCommand(provider)}`,
|
|
251389
|
+
command: exampleCommand(provider)
|
|
251390
|
+
}
|
|
251391
|
+
]
|
|
251392
|
+
});
|
|
251393
|
+
}
|
|
251394
|
+
throw error48;
|
|
251395
|
+
}
|
|
251342
251396
|
const { project: project2 } = await readProjectConfig();
|
|
251343
251397
|
const configDir = dirname11(project2.configPath);
|
|
251344
251398
|
const authDir = join16(configDir, project2.authDir);
|
|
@@ -251348,29 +251402,36 @@ async function ssoEnableAction({ isNonInteractive, runTask: runTask2 }, options)
|
|
|
251348
251402
|
outroMessage: `SSO configured with ${provider} in local config. Run \`base44 auth push\` or \`base44 deploy\` to apply.`
|
|
251349
251403
|
};
|
|
251350
251404
|
}
|
|
251351
|
-
|
|
251352
|
-
|
|
251353
|
-
}
|
|
251405
|
+
function hasEnableOnlyOptions(options) {
|
|
251406
|
+
return Boolean(options.provider || options.clientId || options.clientSecret || options.clientSecretStdin || options.envFile || options.file || options.scope || options.discoveryUrl || options.tenantId || options.oktaDomain || options.authEndpoint || options.tokenEndpoint || options.userinfoEndpoint || options.jwksUri || options.ssoName);
|
|
251407
|
+
}
|
|
251408
|
+
async function ssoDisableAction({ log, runTask: runTask2 }, options) {
|
|
251409
|
+
if (hasEnableOnlyOptions(options)) {
|
|
251410
|
+
throw new InvalidInputError("Configuration options cannot be used with disable. To disable SSO: base44 auth sso disable");
|
|
251411
|
+
}
|
|
251354
251412
|
const { project: project2 } = await readProjectConfig();
|
|
251355
251413
|
const configDir = dirname11(project2.configPath);
|
|
251356
251414
|
const authDir = join16(configDir, project2.authDir);
|
|
251357
|
-
await runTask2("Updating local auth config", async () => updateSSOConfig(authDir, null, false));
|
|
251415
|
+
const updated = await runTask2("Updating local auth config", async () => updateSSOConfig(authDir, null, false));
|
|
251358
251416
|
await runTask2("Removing SSO credentials", async () => deleteSSOSecrets());
|
|
251417
|
+
if (!hasAnyLoginMethod(updated)) {
|
|
251418
|
+
log.warn("Disabling SSO will leave no login methods enabled. Users will be locked out.");
|
|
251419
|
+
}
|
|
251359
251420
|
return {
|
|
251360
251421
|
outroMessage: "SSO disabled in local config and credentials removed. Run `base44 auth push` or `base44 deploy` to apply."
|
|
251361
251422
|
};
|
|
251362
251423
|
}
|
|
251363
251424
|
async function ssoAction(context, action, options) {
|
|
251364
251425
|
if (action === "disable") {
|
|
251365
|
-
return ssoDisableAction(context);
|
|
251426
|
+
return ssoDisableAction(context, options);
|
|
251366
251427
|
}
|
|
251367
251428
|
return ssoEnableAction(context, options);
|
|
251368
251429
|
}
|
|
251369
251430
|
function getSSOCommand() {
|
|
251370
|
-
return new Base44Command("sso").description("Configure SSO identity provider (google, microsoft, github, okta, custom)").addArgument(new Argument("<action>", "enable or disable SSO").choices([
|
|
251431
|
+
return new Base44Command("sso").description("Configure SSO identity provider (google, microsoft, github, okta, custom). SSO and social login are mutually exclusive — enabling one disables the other in the local auth config.").addArgument(new Argument("<action>", "enable or disable SSO").choices([
|
|
251371
251432
|
"enable",
|
|
251372
251433
|
"disable"
|
|
251373
|
-
])).
|
|
251434
|
+
])).addOption(new Option("--provider <provider>", "SSO provider").choices(Object.values(KNOWN_SSO_PROVIDERS))).option("--client-id <id>", "OAuth client ID").option("--client-secret <secret>", "OAuth client secret").option("--client-secret-stdin", "Read client secret from stdin").option("--env-file <path>", "Read client secret from a .env file (key: sso_client_secret)").option("--file <path>", "JSON config file with all SSO settings").option("--scope <scope>", "OAuth scope (defaults per provider)").option("--discovery-url <url>", "OIDC discovery URL").option("--tenant-id <id>", "Microsoft tenant ID (required for microsoft)").option("--okta-domain <domain>", "Okta domain (required for okta)").option("--auth-endpoint <url>", "Authorization endpoint (required for custom)").option("--token-endpoint <url>", "Token endpoint (required for custom)").option("--userinfo-endpoint <url>", "Userinfo endpoint (required for custom)").option("--jwks-uri <url>", "JWKS URI (required for custom)").option("--sso-name <name>", "Provider display name (required for custom)").action(ssoAction);
|
|
251374
251435
|
}
|
|
251375
251436
|
|
|
251376
251437
|
// src/cli/commands/auth/index.ts
|
|
@@ -253632,15 +253693,21 @@ class FunctionManager {
|
|
|
253632
253693
|
this.setupProcessHandlers(name2, process21);
|
|
253633
253694
|
return this.waitForReady(name2, runningFunc);
|
|
253634
253695
|
}
|
|
253635
|
-
reload(functions) {
|
|
253636
|
-
this.stopAll();
|
|
253696
|
+
async reload(functions) {
|
|
253697
|
+
await this.stopAll();
|
|
253637
253698
|
this.functions = new Map(functions.map((f7) => [f7.name, f7]));
|
|
253638
253699
|
}
|
|
253639
|
-
stopAll() {
|
|
253640
|
-
|
|
253700
|
+
async stopAll() {
|
|
253701
|
+
await Promise.all(Array.from(this.running, ([name2, { process: proc2 }]) => {
|
|
253641
253702
|
this.logger.log(`Stopping function: ${name2}`);
|
|
253642
|
-
|
|
253643
|
-
|
|
253703
|
+
const exited = new Promise((r5) => proc2.once("exit", () => r5()));
|
|
253704
|
+
if (process.platform === "win32" && proc2.pid) {
|
|
253705
|
+
spawn2("taskkill", ["/pid", String(proc2.pid), "/T", "/F"]);
|
|
253706
|
+
} else {
|
|
253707
|
+
proc2.kill();
|
|
253708
|
+
}
|
|
253709
|
+
return exited;
|
|
253710
|
+
}));
|
|
253644
253711
|
this.running.clear();
|
|
253645
253712
|
this.starting.clear();
|
|
253646
253713
|
}
|
|
@@ -254028,6 +254095,9 @@ class Database {
|
|
|
254028
254095
|
getCollection(name2) {
|
|
254029
254096
|
return this.collections.get(this.normalizeName(name2));
|
|
254030
254097
|
}
|
|
254098
|
+
getSchema(entityName) {
|
|
254099
|
+
return this.schemas.get(this.normalizeName(entityName));
|
|
254100
|
+
}
|
|
254031
254101
|
getCollectionNames() {
|
|
254032
254102
|
return Array.from(this.collections.keys()).filter((name2) => {
|
|
254033
254103
|
return !name2.startsWith(PRIVATE_COLLECTION_PREFIX);
|
|
@@ -254255,6 +254325,120 @@ In order to complete registration use this verification code: ${otpCode}
|
|
|
254255
254325
|
// src/cli/dev/dev-server/routes/entities/entities-router.ts
|
|
254256
254326
|
var import_express4 = __toESM(require_express(), 1);
|
|
254257
254327
|
|
|
254328
|
+
// src/cli/dev/dev-server/db/rls.ts
|
|
254329
|
+
function getRLSFieldValue(key2, source3) {
|
|
254330
|
+
const DATA_FIELD_PREFIX = "data.";
|
|
254331
|
+
return source3[key2.startsWith(DATA_FIELD_PREFIX) ? key2.slice(DATA_FIELD_PREFIX.length) : key2];
|
|
254332
|
+
}
|
|
254333
|
+
function resolveTemplate(value, user) {
|
|
254334
|
+
return value.replace(/\{\{user\.([\w.]+)\}\}/g, (_match, path18) => {
|
|
254335
|
+
return String(getRLSFieldValue(path18, user) ?? "");
|
|
254336
|
+
});
|
|
254337
|
+
}
|
|
254338
|
+
function evaluateOperator(recordValue, operator) {
|
|
254339
|
+
for (const [op2, opValue] of Object.entries(operator)) {
|
|
254340
|
+
switch (op2) {
|
|
254341
|
+
case "$in":
|
|
254342
|
+
if (!Array.isArray(opValue) || !opValue.includes(recordValue)) {
|
|
254343
|
+
return false;
|
|
254344
|
+
}
|
|
254345
|
+
break;
|
|
254346
|
+
case "$nin":
|
|
254347
|
+
if (!Array.isArray(opValue) || opValue.includes(recordValue)) {
|
|
254348
|
+
return false;
|
|
254349
|
+
}
|
|
254350
|
+
break;
|
|
254351
|
+
case "$ne":
|
|
254352
|
+
if (recordValue === opValue)
|
|
254353
|
+
return false;
|
|
254354
|
+
break;
|
|
254355
|
+
case "$all":
|
|
254356
|
+
if (!Array.isArray(recordValue) || !Array.isArray(opValue)) {
|
|
254357
|
+
return false;
|
|
254358
|
+
}
|
|
254359
|
+
if (!opValue.every((v10) => recordValue.includes(v10))) {
|
|
254360
|
+
return false;
|
|
254361
|
+
}
|
|
254362
|
+
break;
|
|
254363
|
+
default:
|
|
254364
|
+
return false;
|
|
254365
|
+
}
|
|
254366
|
+
}
|
|
254367
|
+
return true;
|
|
254368
|
+
}
|
|
254369
|
+
function evaluateUserCondition(condition, user) {
|
|
254370
|
+
for (const [key2, expected] of Object.entries(condition)) {
|
|
254371
|
+
const userValue = getRLSFieldValue(key2, user);
|
|
254372
|
+
if (typeof expected === "object" && expected !== null) {
|
|
254373
|
+
if (!evaluateOperator(userValue, expected))
|
|
254374
|
+
return false;
|
|
254375
|
+
} else {
|
|
254376
|
+
if (userValue !== expected)
|
|
254377
|
+
return false;
|
|
254378
|
+
}
|
|
254379
|
+
}
|
|
254380
|
+
return true;
|
|
254381
|
+
}
|
|
254382
|
+
function evaluateCondition(condition, record2, user) {
|
|
254383
|
+
for (const [key2, value] of Object.entries(condition)) {
|
|
254384
|
+
if (key2 === "user_condition") {
|
|
254385
|
+
if (!evaluateUserCondition(value, user))
|
|
254386
|
+
return false;
|
|
254387
|
+
continue;
|
|
254388
|
+
}
|
|
254389
|
+
if (key2 === "$or") {
|
|
254390
|
+
const conditions = value;
|
|
254391
|
+
if (!conditions.some((c8) => evaluateCondition(c8, record2, user)))
|
|
254392
|
+
return false;
|
|
254393
|
+
continue;
|
|
254394
|
+
}
|
|
254395
|
+
if (key2 === "$and") {
|
|
254396
|
+
const conditions = value;
|
|
254397
|
+
if (!conditions.every((c8) => evaluateCondition(c8, record2, user)))
|
|
254398
|
+
return false;
|
|
254399
|
+
continue;
|
|
254400
|
+
}
|
|
254401
|
+
if (key2 === "$nor") {
|
|
254402
|
+
const conditions = value;
|
|
254403
|
+
if (conditions.some((c8) => evaluateCondition(c8, record2, user)))
|
|
254404
|
+
return false;
|
|
254405
|
+
continue;
|
|
254406
|
+
}
|
|
254407
|
+
const recordValue = getRLSFieldValue(key2, record2);
|
|
254408
|
+
const resolvedValue = typeof value === "string" ? resolveTemplate(value, user) : value;
|
|
254409
|
+
if (typeof resolvedValue === "object" && resolvedValue !== null) {
|
|
254410
|
+
if (!evaluateOperator(recordValue, resolvedValue))
|
|
254411
|
+
return false;
|
|
254412
|
+
} else {
|
|
254413
|
+
if (recordValue !== resolvedValue)
|
|
254414
|
+
return false;
|
|
254415
|
+
}
|
|
254416
|
+
}
|
|
254417
|
+
return true;
|
|
254418
|
+
}
|
|
254419
|
+
function checkRLS(rule, record2, user) {
|
|
254420
|
+
if (rule === undefined)
|
|
254421
|
+
return true;
|
|
254422
|
+
if (typeof rule === "boolean")
|
|
254423
|
+
return rule;
|
|
254424
|
+
if (!user)
|
|
254425
|
+
return false;
|
|
254426
|
+
return evaluateCondition(rule, record2, user);
|
|
254427
|
+
}
|
|
254428
|
+
function applyFLS(record2, schema10, user, operation) {
|
|
254429
|
+
if (Array.isArray(record2)) {
|
|
254430
|
+
return record2.map((r5) => applyFLS(r5, schema10, user, operation));
|
|
254431
|
+
}
|
|
254432
|
+
const result = {};
|
|
254433
|
+
for (const [key2, value] of Object.entries(record2)) {
|
|
254434
|
+
const rule = schema10.properties[key2]?.rls?.[operation];
|
|
254435
|
+
if (!rule || checkRLS(rule, record2, user)) {
|
|
254436
|
+
result[key2] = value;
|
|
254437
|
+
}
|
|
254438
|
+
}
|
|
254439
|
+
return result;
|
|
254440
|
+
}
|
|
254441
|
+
|
|
254258
254442
|
// src/cli/dev/dev-server/db/entity-queries.ts
|
|
254259
254443
|
function parseSort(sort) {
|
|
254260
254444
|
if (!sort) {
|
|
@@ -254312,30 +254496,54 @@ var queryEntity = async (collection, reqQuery) => {
|
|
|
254312
254496
|
return cursor3;
|
|
254313
254497
|
};
|
|
254314
254498
|
|
|
254499
|
+
// src/cli/dev/dev-server/routes/entities/current-user.ts
|
|
254500
|
+
var import_jsonwebtoken2 = __toESM(require_jsonwebtoken(), 1);
|
|
254501
|
+
function getSubject(payload) {
|
|
254502
|
+
if (!payload || typeof payload === "string") {
|
|
254503
|
+
return;
|
|
254504
|
+
}
|
|
254505
|
+
return payload.sub;
|
|
254506
|
+
}
|
|
254507
|
+
async function resolveCurrentUser(db2, req) {
|
|
254508
|
+
const auth2 = req.headers.authorization;
|
|
254509
|
+
if (!auth2?.startsWith("Bearer ")) {
|
|
254510
|
+
return { ok: false, reason: "missing" };
|
|
254511
|
+
}
|
|
254512
|
+
try {
|
|
254513
|
+
const decoded = import_jsonwebtoken2.default.decode(auth2.replace("Bearer ", ""), {
|
|
254514
|
+
complete: true
|
|
254515
|
+
});
|
|
254516
|
+
const subject = getSubject(decoded?.payload);
|
|
254517
|
+
if (!subject) {
|
|
254518
|
+
return { ok: false, reason: "invalid" };
|
|
254519
|
+
}
|
|
254520
|
+
const currentUser = await db2.getCollection(USER_COLLECTION)?.findOneAsync({ email: subject });
|
|
254521
|
+
if (!currentUser) {
|
|
254522
|
+
return { ok: false, reason: "not_found" };
|
|
254523
|
+
}
|
|
254524
|
+
return { ok: true, user: currentUser };
|
|
254525
|
+
} catch {
|
|
254526
|
+
return { ok: false, reason: "invalid" };
|
|
254527
|
+
}
|
|
254528
|
+
}
|
|
254529
|
+
|
|
254315
254530
|
// src/cli/dev/dev-server/routes/entities/entities-user-router.ts
|
|
254316
254531
|
var import_express3 = __toESM(require_express(), 1);
|
|
254317
|
-
var import_jsonwebtoken2 = __toESM(require_jsonwebtoken(), 1);
|
|
254318
254532
|
function createUserRouter(db2, logger2) {
|
|
254319
254533
|
const router = import_express3.Router({ mergeParams: true });
|
|
254320
254534
|
const parseBody = import_express3.json();
|
|
254321
254535
|
function withAuth(handler) {
|
|
254322
254536
|
return async (req, res) => {
|
|
254323
|
-
const
|
|
254324
|
-
if (!
|
|
254537
|
+
const currentUserResult = await resolveCurrentUser(db2, req);
|
|
254538
|
+
if (!currentUserResult.ok && (currentUserResult.reason === "missing" || currentUserResult.reason === "invalid")) {
|
|
254325
254539
|
res.status(401).json({ error: "Unauthorized" });
|
|
254326
254540
|
return;
|
|
254327
254541
|
}
|
|
254328
|
-
|
|
254329
|
-
|
|
254330
|
-
|
|
254331
|
-
if (!result) {
|
|
254332
|
-
res.status(404).json({ error: "Unable to read data for the current user" });
|
|
254333
|
-
return;
|
|
254334
|
-
}
|
|
254335
|
-
await handler(req, res, result);
|
|
254336
|
-
} catch {
|
|
254337
|
-
res.status(401).json({ error: "Unauthorized" });
|
|
254542
|
+
if (!currentUserResult.ok) {
|
|
254543
|
+
res.status(404).json({ error: "Unable to read data for the current user" });
|
|
254544
|
+
return;
|
|
254338
254545
|
}
|
|
254546
|
+
await handler(req, res, currentUserResult.user);
|
|
254339
254547
|
};
|
|
254340
254548
|
}
|
|
254341
254549
|
router.get("/:id", withAuth(async (req, res, currentUser) => {
|
|
@@ -254435,12 +254643,20 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254435
254643
|
const parseBody = import_express4.json();
|
|
254436
254644
|
function withCollection(handler) {
|
|
254437
254645
|
return async (req, res) => {
|
|
254438
|
-
const
|
|
254646
|
+
const { entityName } = req.params;
|
|
254647
|
+
const collection = db2.getCollection(entityName);
|
|
254439
254648
|
if (!collection) {
|
|
254440
|
-
res.status(404).json({ error: `Entity "${
|
|
254649
|
+
res.status(404).json({ error: `Entity "${entityName}" not found` });
|
|
254650
|
+
return;
|
|
254651
|
+
}
|
|
254652
|
+
const schema10 = db2.getSchema(entityName);
|
|
254653
|
+
if (!schema10) {
|
|
254654
|
+
res.status(404).json({ error: `Schema for "${entityName}" not found` });
|
|
254441
254655
|
return;
|
|
254442
254656
|
}
|
|
254443
|
-
await
|
|
254657
|
+
const currentUserResult = await resolveCurrentUser(db2, req);
|
|
254658
|
+
const currentUser = currentUserResult.ok ? currentUserResult.user : undefined;
|
|
254659
|
+
await handler(req, res, collection, schema10, currentUser);
|
|
254444
254660
|
};
|
|
254445
254661
|
}
|
|
254446
254662
|
function emit(appId, entityName, type, data) {
|
|
@@ -254458,9 +254674,31 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254458
254674
|
}
|
|
254459
254675
|
broadcast(appId, entityName, createData(data));
|
|
254460
254676
|
}
|
|
254677
|
+
function prepareCreateRecord(entityName, body, schema10, currentUser, now) {
|
|
254678
|
+
const { _id, ...recordBody } = body;
|
|
254679
|
+
const ownerFields = {
|
|
254680
|
+
created_by: currentUser?.email,
|
|
254681
|
+
created_by_id: currentUser?.id
|
|
254682
|
+
};
|
|
254683
|
+
if (!checkRLS(schema10.rls?.create, {
|
|
254684
|
+
...recordBody,
|
|
254685
|
+
...ownerFields
|
|
254686
|
+
}, currentUser)) {
|
|
254687
|
+
return;
|
|
254688
|
+
}
|
|
254689
|
+
const filteredBody = applyFLS(db2.prepareRecord(entityName, recordBody), schema10, currentUser, "write");
|
|
254690
|
+
db2.validate(entityName, filteredBody);
|
|
254691
|
+
return {
|
|
254692
|
+
...filteredBody,
|
|
254693
|
+
id: nanoid3(),
|
|
254694
|
+
...ownerFields,
|
|
254695
|
+
created_date: now,
|
|
254696
|
+
updated_date: now
|
|
254697
|
+
};
|
|
254698
|
+
}
|
|
254461
254699
|
const userRouter = createUserRouter(db2, logger2);
|
|
254462
254700
|
router.use("/User", userRouter);
|
|
254463
|
-
router.get("/:entityName/:id", withCollection(async (req, res, collection) => {
|
|
254701
|
+
router.get("/:entityName/:id", withCollection(async (req, res, collection, schema10, currentUser) => {
|
|
254464
254702
|
const { entityName, id: id2 } = req.params;
|
|
254465
254703
|
try {
|
|
254466
254704
|
const doc2 = await collection.findOneAsync({ id: id2 });
|
|
@@ -254468,16 +254706,28 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254468
254706
|
res.status(404).json({ error: `Record with id "${id2}" not found` });
|
|
254469
254707
|
return;
|
|
254470
254708
|
}
|
|
254471
|
-
|
|
254709
|
+
if (!checkRLS(schema10.rls?.read, doc2, currentUser)) {
|
|
254710
|
+
res.status(404).json({
|
|
254711
|
+
message: `Entity ${entityName} with ID ${id2} not found`
|
|
254712
|
+
});
|
|
254713
|
+
return;
|
|
254714
|
+
}
|
|
254715
|
+
const result = applyFLS(stripInternalFields(doc2), schema10, currentUser, "read");
|
|
254716
|
+
res.json(result);
|
|
254472
254717
|
} catch (error48) {
|
|
254473
254718
|
logger2.error(`Error in GET /${entityName}/${id2}:`, error48);
|
|
254474
254719
|
res.status(500).json({ error: "Internal server error" });
|
|
254475
254720
|
}
|
|
254476
254721
|
}));
|
|
254477
|
-
router.get("/:entityName", withCollection(async (req, res, collection) => {
|
|
254722
|
+
router.get("/:entityName", withCollection(async (req, res, collection, schema10, currentUser) => {
|
|
254478
254723
|
const { entityName } = req.params;
|
|
254479
254724
|
try {
|
|
254480
|
-
|
|
254725
|
+
let results = stripInternalFields(await queryEntity(collection, req.query));
|
|
254726
|
+
if (schema10.rls?.read && schema10.rls.read !== true) {
|
|
254727
|
+
results = results.filter((doc2) => checkRLS(schema10.rls.read, doc2, currentUser));
|
|
254728
|
+
}
|
|
254729
|
+
results = results.map((doc2) => applyFLS(doc2, schema10, currentUser, "read"));
|
|
254730
|
+
res.json(results);
|
|
254481
254731
|
} catch (error48) {
|
|
254482
254732
|
if (error48 instanceof InvalidInputError) {
|
|
254483
254733
|
res.status(400).json({ error: error48.message });
|
|
@@ -254487,20 +254737,16 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254487
254737
|
}
|
|
254488
254738
|
}
|
|
254489
254739
|
}));
|
|
254490
|
-
router.post("/:entityName", parseBody, withCollection(async (req, res, collection) => {
|
|
254740
|
+
router.post("/:entityName", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
|
|
254491
254741
|
const { appId, entityName } = req.params;
|
|
254492
254742
|
try {
|
|
254493
254743
|
const now = new Date().toISOString();
|
|
254494
|
-
const
|
|
254495
|
-
|
|
254496
|
-
|
|
254497
|
-
|
|
254498
|
-
|
|
254499
|
-
|
|
254500
|
-
created_date: now,
|
|
254501
|
-
updated_date: now
|
|
254502
|
-
};
|
|
254503
|
-
const inserted = stripInternalFields(await collection.insertAsync(record2));
|
|
254744
|
+
const record2 = prepareCreateRecord(entityName, req.body, schema10, currentUser, now);
|
|
254745
|
+
if (!record2) {
|
|
254746
|
+
res.status(403).json({ error: "Permission denied" });
|
|
254747
|
+
return;
|
|
254748
|
+
}
|
|
254749
|
+
const inserted = applyFLS(stripInternalFields(await collection.insertAsync(record2)), schema10, currentUser, "read");
|
|
254504
254750
|
emit(appId, entityName, "create", inserted);
|
|
254505
254751
|
res.status(201).json(inserted);
|
|
254506
254752
|
} catch (error48) {
|
|
@@ -254512,7 +254758,7 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254512
254758
|
res.status(500).json({ error: "Internal server error" });
|
|
254513
254759
|
}
|
|
254514
254760
|
}));
|
|
254515
|
-
router.post("/:entityName/bulk", parseBody, withCollection(async (req, res, collection) => {
|
|
254761
|
+
router.post("/:entityName/bulk", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
|
|
254516
254762
|
const { appId, entityName } = req.params;
|
|
254517
254763
|
if (!Array.isArray(req.body)) {
|
|
254518
254764
|
res.status(400).json({ error: "Request body must be an array" });
|
|
@@ -254521,17 +254767,15 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254521
254767
|
try {
|
|
254522
254768
|
const now = new Date().toISOString();
|
|
254523
254769
|
const records = [];
|
|
254524
|
-
for (const
|
|
254525
|
-
const
|
|
254526
|
-
|
|
254527
|
-
|
|
254528
|
-
|
|
254529
|
-
|
|
254530
|
-
|
|
254531
|
-
updated_date: now
|
|
254532
|
-
});
|
|
254770
|
+
for (const body of req.body) {
|
|
254771
|
+
const record2 = prepareCreateRecord(entityName, body, schema10, currentUser, now);
|
|
254772
|
+
if (!record2) {
|
|
254773
|
+
res.status(403).json({ error: "Permission denied" });
|
|
254774
|
+
return;
|
|
254775
|
+
}
|
|
254776
|
+
records.push(record2);
|
|
254533
254777
|
}
|
|
254534
|
-
const inserted = stripInternalFields(await collection.insertAsync(records));
|
|
254778
|
+
const inserted = applyFLS(stripInternalFields(await collection.insertAsync(records)), schema10, currentUser, "read");
|
|
254535
254779
|
emit(appId, entityName, "create", inserted);
|
|
254536
254780
|
res.status(201).json(inserted);
|
|
254537
254781
|
} catch (error48) {
|
|
@@ -254543,11 +254787,24 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254543
254787
|
res.status(500).json({ error: "Internal server error" });
|
|
254544
254788
|
}
|
|
254545
254789
|
}));
|
|
254546
|
-
router.put("/:entityName/:id", parseBody, withCollection(async (req, res, collection) => {
|
|
254790
|
+
router.put("/:entityName/:id", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
|
|
254547
254791
|
const { appId, entityName, id: id2 } = req.params;
|
|
254548
254792
|
const { id: _id, created_date: _created_date, ...body } = req.body;
|
|
254549
254793
|
try {
|
|
254550
|
-
|
|
254794
|
+
if (schema10.rls?.update !== undefined) {
|
|
254795
|
+
const existing = await collection.findOneAsync({ id: id2 });
|
|
254796
|
+
if (!existing) {
|
|
254797
|
+
res.status(404).json({ error: `Record with id "${id2}" not found` });
|
|
254798
|
+
return;
|
|
254799
|
+
}
|
|
254800
|
+
if (!checkRLS(schema10.rls.update, existing, currentUser)) {
|
|
254801
|
+
res.status(404).json({
|
|
254802
|
+
message: `Entity ${entityName} with ID ${id2} not found`
|
|
254803
|
+
});
|
|
254804
|
+
return;
|
|
254805
|
+
}
|
|
254806
|
+
}
|
|
254807
|
+
const filteredBody = applyFLS(db2.prepareRecord(entityName, body, true), schema10, currentUser, "write");
|
|
254551
254808
|
db2.validate(entityName, filteredBody, true);
|
|
254552
254809
|
const updateData = {
|
|
254553
254810
|
...filteredBody,
|
|
@@ -254558,7 +254815,7 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254558
254815
|
res.status(404).json({ error: `Record with id "${id2}" not found` });
|
|
254559
254816
|
return;
|
|
254560
254817
|
}
|
|
254561
|
-
const updated = stripInternalFields(result.affectedDocuments);
|
|
254818
|
+
const updated = applyFLS(stripInternalFields(result.affectedDocuments), schema10, currentUser, "read");
|
|
254562
254819
|
emit(appId, entityName, "update", updated);
|
|
254563
254820
|
res.json(updated);
|
|
254564
254821
|
} catch (error48) {
|
|
@@ -254570,30 +254827,48 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254570
254827
|
res.status(500).json({ error: "Internal server error" });
|
|
254571
254828
|
}
|
|
254572
254829
|
}));
|
|
254573
|
-
router.delete("/:entityName/:id", withCollection(async (req, res, collection) => {
|
|
254830
|
+
router.delete("/:entityName/:id", withCollection(async (req, res, collection, schema10, currentUser) => {
|
|
254574
254831
|
const { appId, entityName, id: id2 } = req.params;
|
|
254575
254832
|
try {
|
|
254576
254833
|
const doc2 = await collection.findOneAsync({ id: id2 });
|
|
254577
|
-
|
|
254578
|
-
if (numRemoved === 0) {
|
|
254834
|
+
if (!doc2) {
|
|
254579
254835
|
res.status(404).json({ error: `Record with id "${id2}" not found` });
|
|
254580
254836
|
return;
|
|
254581
254837
|
}
|
|
254582
|
-
if (doc2) {
|
|
254583
|
-
|
|
254838
|
+
if (!checkRLS(schema10.rls?.delete, doc2, currentUser)) {
|
|
254839
|
+
res.status(404).json({
|
|
254840
|
+
message: `Entity ${entityName} with ID ${id2} not found`
|
|
254841
|
+
});
|
|
254842
|
+
return;
|
|
254584
254843
|
}
|
|
254844
|
+
await collection.removeAsync({ id: id2 }, { multi: false });
|
|
254845
|
+
emit(appId, entityName, "delete", stripInternalFields(doc2));
|
|
254585
254846
|
res.json({ success: true });
|
|
254586
254847
|
} catch (error48) {
|
|
254587
254848
|
logger2.error(`Error in DELETE /${entityName}/${id2}:`, error48);
|
|
254588
254849
|
res.status(500).json({ error: "Internal server error" });
|
|
254589
254850
|
}
|
|
254590
254851
|
}));
|
|
254591
|
-
router.delete("/:entityName", parseBody, withCollection(async (req, res, collection) => {
|
|
254852
|
+
router.delete("/:entityName", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
|
|
254592
254853
|
const { entityName } = req.params;
|
|
254593
254854
|
try {
|
|
254594
254855
|
const query = req.body || {};
|
|
254595
|
-
const
|
|
254596
|
-
|
|
254856
|
+
const rlsDelete = schema10?.rls?.delete;
|
|
254857
|
+
if (rlsDelete !== undefined && rlsDelete !== true) {
|
|
254858
|
+
if (rlsDelete === false) {
|
|
254859
|
+
res.status(403).json({ error: "Permission denied" });
|
|
254860
|
+
return;
|
|
254861
|
+
}
|
|
254862
|
+
const docs = await collection.findAsync(query);
|
|
254863
|
+
const allowedIds = docs.filter((doc2) => checkRLS(rlsDelete, doc2, currentUser)).map((doc2) => doc2.id);
|
|
254864
|
+
const numRemoved = await collection.removeAsync({ id: { $in: allowedIds } }, { multi: true });
|
|
254865
|
+
res.json({ success: true, deleted: numRemoved });
|
|
254866
|
+
} else {
|
|
254867
|
+
const numRemoved = await collection.removeAsync(query, {
|
|
254868
|
+
multi: true
|
|
254869
|
+
});
|
|
254870
|
+
res.json({ success: true, deleted: numRemoved });
|
|
254871
|
+
}
|
|
254597
254872
|
} catch (error48) {
|
|
254598
254873
|
logger2.error(`Error in DELETE /${entityName}:`, error48);
|
|
254599
254874
|
res.status(500).json({ error: "Internal server error" });
|
|
@@ -256393,7 +256668,9 @@ var DEFAULT_PORT = 4400;
|
|
|
256393
256668
|
var BASE44_APP_URL = "https://base44.app";
|
|
256394
256669
|
async function createDevServer(options8) {
|
|
256395
256670
|
const { port: userPort } = options8;
|
|
256396
|
-
const port = userPort ?? await getPorts({
|
|
256671
|
+
const port = userPort ?? await getPorts({
|
|
256672
|
+
port: process.env.IS_TEST === "true" ? undefined : DEFAULT_PORT
|
|
256673
|
+
});
|
|
256397
256674
|
const baseUrl = `http://localhost:${port}`;
|
|
256398
256675
|
const { functions, entities, project: project2 } = await options8.loadResources();
|
|
256399
256676
|
const app = import_express6.default();
|
|
@@ -256486,7 +256763,7 @@ async function createDevServer(options8) {
|
|
|
256486
256763
|
const { functions: functions2, entities: entities2 } = await options8.loadResources();
|
|
256487
256764
|
if (name2 === "functions") {
|
|
256488
256765
|
const previousFunctionCount = functionManager.getFunctionNames().length;
|
|
256489
|
-
functionManager.reload(functions2);
|
|
256766
|
+
await functionManager.reload(functions2);
|
|
256490
256767
|
const names = functionManager.getFunctionNames();
|
|
256491
256768
|
if (names.length > 0) {
|
|
256492
256769
|
devLogger.log(`Reloaded functions: ${names.sort().join(", ")}`);
|
|
@@ -256511,10 +256788,10 @@ async function createDevServer(options8) {
|
|
|
256511
256788
|
}
|
|
256512
256789
|
});
|
|
256513
256790
|
await base44ConfigWatcher.start();
|
|
256514
|
-
const shutdown = () => {
|
|
256791
|
+
const shutdown = async () => {
|
|
256515
256792
|
base44ConfigWatcher.close();
|
|
256516
256793
|
io6.close();
|
|
256517
|
-
functionManager.stopAll();
|
|
256794
|
+
await functionManager.stopAll();
|
|
256518
256795
|
server.close();
|
|
256519
256796
|
};
|
|
256520
256797
|
process.on("SIGINT", shutdown);
|
|
@@ -261005,4 +261282,4 @@ export {
|
|
|
261005
261282
|
CLIExitError
|
|
261006
261283
|
};
|
|
261007
261284
|
|
|
261008
|
-
//# debugId=
|
|
261285
|
+
//# debugId=827C7659385FB39764756E2164756E21
|