@base44-preview/cli 0.0.50-pr.487.fc1c45a → 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 +335 -84
- 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,8 +251274,21 @@ 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
|
+
};
|
|
251265
251290
|
function secretKeyToFlag(key) {
|
|
251266
|
-
return
|
|
251291
|
+
return SECRET_KEY_TO_FLAG[key];
|
|
251267
251292
|
}
|
|
251268
251293
|
function exampleCommand(provider) {
|
|
251269
251294
|
let cmd = `base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`;
|
|
@@ -251289,6 +251314,9 @@ function validateProvider(provider) {
|
|
|
251289
251314
|
return provider;
|
|
251290
251315
|
}
|
|
251291
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
|
+
}
|
|
251292
251320
|
let merged = options;
|
|
251293
251321
|
if (options.file) {
|
|
251294
251322
|
const fileConfig = await loadSSOConfigFile(options.file);
|
|
@@ -251352,9 +251380,9 @@ async function ssoEnableAction({ isNonInteractive, runTask: runTask2 }, options)
|
|
|
251352
251380
|
try {
|
|
251353
251381
|
secrets = buildSSOSecrets(provider, secretOptions);
|
|
251354
251382
|
} catch (error48) {
|
|
251355
|
-
if (error48 instanceof
|
|
251356
|
-
const
|
|
251357
|
-
throw new InvalidInputError(
|
|
251383
|
+
if (error48 instanceof MissingSSOFieldsError) {
|
|
251384
|
+
const flagNames = error48.missingKeys.map(secretKeyToFlag);
|
|
251385
|
+
throw new InvalidInputError(`Missing required fields for ${error48.provider}: ${flagNames.join(", ")}`, {
|
|
251358
251386
|
hints: [
|
|
251359
251387
|
{
|
|
251360
251388
|
message: `Example: ${exampleCommand(provider)}`,
|
|
@@ -251374,26 +251402,33 @@ async function ssoEnableAction({ isNonInteractive, runTask: runTask2 }, options)
|
|
|
251374
251402
|
outroMessage: `SSO configured with ${provider} in local config. Run \`base44 auth push\` or \`base44 deploy\` to apply.`
|
|
251375
251403
|
};
|
|
251376
251404
|
}
|
|
251377
|
-
|
|
251378
|
-
|
|
251379
|
-
}
|
|
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
|
+
}
|
|
251380
251412
|
const { project: project2 } = await readProjectConfig();
|
|
251381
251413
|
const configDir = dirname11(project2.configPath);
|
|
251382
251414
|
const authDir = join16(configDir, project2.authDir);
|
|
251383
|
-
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));
|
|
251384
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
|
+
}
|
|
251385
251420
|
return {
|
|
251386
251421
|
outroMessage: "SSO disabled in local config and credentials removed. Run `base44 auth push` or `base44 deploy` to apply."
|
|
251387
251422
|
};
|
|
251388
251423
|
}
|
|
251389
251424
|
async function ssoAction(context, action, options) {
|
|
251390
251425
|
if (action === "disable") {
|
|
251391
|
-
return ssoDisableAction(context);
|
|
251426
|
+
return ssoDisableAction(context, options);
|
|
251392
251427
|
}
|
|
251393
251428
|
return ssoEnableAction(context, options);
|
|
251394
251429
|
}
|
|
251395
251430
|
function getSSOCommand() {
|
|
251396
|
-
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([
|
|
251397
251432
|
"enable",
|
|
251398
251433
|
"disable"
|
|
251399
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);
|
|
@@ -253658,15 +253693,21 @@ class FunctionManager {
|
|
|
253658
253693
|
this.setupProcessHandlers(name2, process21);
|
|
253659
253694
|
return this.waitForReady(name2, runningFunc);
|
|
253660
253695
|
}
|
|
253661
|
-
reload(functions) {
|
|
253662
|
-
this.stopAll();
|
|
253696
|
+
async reload(functions) {
|
|
253697
|
+
await this.stopAll();
|
|
253663
253698
|
this.functions = new Map(functions.map((f7) => [f7.name, f7]));
|
|
253664
253699
|
}
|
|
253665
|
-
stopAll() {
|
|
253666
|
-
|
|
253700
|
+
async stopAll() {
|
|
253701
|
+
await Promise.all(Array.from(this.running, ([name2, { process: proc2 }]) => {
|
|
253667
253702
|
this.logger.log(`Stopping function: ${name2}`);
|
|
253668
|
-
|
|
253669
|
-
|
|
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
|
+
}));
|
|
253670
253711
|
this.running.clear();
|
|
253671
253712
|
this.starting.clear();
|
|
253672
253713
|
}
|
|
@@ -254054,6 +254095,9 @@ class Database {
|
|
|
254054
254095
|
getCollection(name2) {
|
|
254055
254096
|
return this.collections.get(this.normalizeName(name2));
|
|
254056
254097
|
}
|
|
254098
|
+
getSchema(entityName) {
|
|
254099
|
+
return this.schemas.get(this.normalizeName(entityName));
|
|
254100
|
+
}
|
|
254057
254101
|
getCollectionNames() {
|
|
254058
254102
|
return Array.from(this.collections.keys()).filter((name2) => {
|
|
254059
254103
|
return !name2.startsWith(PRIVATE_COLLECTION_PREFIX);
|
|
@@ -254281,6 +254325,120 @@ In order to complete registration use this verification code: ${otpCode}
|
|
|
254281
254325
|
// src/cli/dev/dev-server/routes/entities/entities-router.ts
|
|
254282
254326
|
var import_express4 = __toESM(require_express(), 1);
|
|
254283
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
|
+
|
|
254284
254442
|
// src/cli/dev/dev-server/db/entity-queries.ts
|
|
254285
254443
|
function parseSort(sort) {
|
|
254286
254444
|
if (!sort) {
|
|
@@ -254338,30 +254496,54 @@ var queryEntity = async (collection, reqQuery) => {
|
|
|
254338
254496
|
return cursor3;
|
|
254339
254497
|
};
|
|
254340
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
|
+
|
|
254341
254530
|
// src/cli/dev/dev-server/routes/entities/entities-user-router.ts
|
|
254342
254531
|
var import_express3 = __toESM(require_express(), 1);
|
|
254343
|
-
var import_jsonwebtoken2 = __toESM(require_jsonwebtoken(), 1);
|
|
254344
254532
|
function createUserRouter(db2, logger2) {
|
|
254345
254533
|
const router = import_express3.Router({ mergeParams: true });
|
|
254346
254534
|
const parseBody = import_express3.json();
|
|
254347
254535
|
function withAuth(handler) {
|
|
254348
254536
|
return async (req, res) => {
|
|
254349
|
-
const
|
|
254350
|
-
if (!
|
|
254537
|
+
const currentUserResult = await resolveCurrentUser(db2, req);
|
|
254538
|
+
if (!currentUserResult.ok && (currentUserResult.reason === "missing" || currentUserResult.reason === "invalid")) {
|
|
254351
254539
|
res.status(401).json({ error: "Unauthorized" });
|
|
254352
254540
|
return;
|
|
254353
254541
|
}
|
|
254354
|
-
|
|
254355
|
-
|
|
254356
|
-
|
|
254357
|
-
if (!result) {
|
|
254358
|
-
res.status(404).json({ error: "Unable to read data for the current user" });
|
|
254359
|
-
return;
|
|
254360
|
-
}
|
|
254361
|
-
await handler(req, res, result);
|
|
254362
|
-
} catch {
|
|
254363
|
-
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;
|
|
254364
254545
|
}
|
|
254546
|
+
await handler(req, res, currentUserResult.user);
|
|
254365
254547
|
};
|
|
254366
254548
|
}
|
|
254367
254549
|
router.get("/:id", withAuth(async (req, res, currentUser) => {
|
|
@@ -254461,12 +254643,20 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254461
254643
|
const parseBody = import_express4.json();
|
|
254462
254644
|
function withCollection(handler) {
|
|
254463
254645
|
return async (req, res) => {
|
|
254464
|
-
const
|
|
254646
|
+
const { entityName } = req.params;
|
|
254647
|
+
const collection = db2.getCollection(entityName);
|
|
254465
254648
|
if (!collection) {
|
|
254466
|
-
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` });
|
|
254467
254655
|
return;
|
|
254468
254656
|
}
|
|
254469
|
-
await
|
|
254657
|
+
const currentUserResult = await resolveCurrentUser(db2, req);
|
|
254658
|
+
const currentUser = currentUserResult.ok ? currentUserResult.user : undefined;
|
|
254659
|
+
await handler(req, res, collection, schema10, currentUser);
|
|
254470
254660
|
};
|
|
254471
254661
|
}
|
|
254472
254662
|
function emit(appId, entityName, type, data) {
|
|
@@ -254484,9 +254674,31 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254484
254674
|
}
|
|
254485
254675
|
broadcast(appId, entityName, createData(data));
|
|
254486
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
|
+
}
|
|
254487
254699
|
const userRouter = createUserRouter(db2, logger2);
|
|
254488
254700
|
router.use("/User", userRouter);
|
|
254489
|
-
router.get("/:entityName/:id", withCollection(async (req, res, collection) => {
|
|
254701
|
+
router.get("/:entityName/:id", withCollection(async (req, res, collection, schema10, currentUser) => {
|
|
254490
254702
|
const { entityName, id: id2 } = req.params;
|
|
254491
254703
|
try {
|
|
254492
254704
|
const doc2 = await collection.findOneAsync({ id: id2 });
|
|
@@ -254494,16 +254706,28 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254494
254706
|
res.status(404).json({ error: `Record with id "${id2}" not found` });
|
|
254495
254707
|
return;
|
|
254496
254708
|
}
|
|
254497
|
-
|
|
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);
|
|
254498
254717
|
} catch (error48) {
|
|
254499
254718
|
logger2.error(`Error in GET /${entityName}/${id2}:`, error48);
|
|
254500
254719
|
res.status(500).json({ error: "Internal server error" });
|
|
254501
254720
|
}
|
|
254502
254721
|
}));
|
|
254503
|
-
router.get("/:entityName", withCollection(async (req, res, collection) => {
|
|
254722
|
+
router.get("/:entityName", withCollection(async (req, res, collection, schema10, currentUser) => {
|
|
254504
254723
|
const { entityName } = req.params;
|
|
254505
254724
|
try {
|
|
254506
|
-
|
|
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);
|
|
254507
254731
|
} catch (error48) {
|
|
254508
254732
|
if (error48 instanceof InvalidInputError) {
|
|
254509
254733
|
res.status(400).json({ error: error48.message });
|
|
@@ -254513,20 +254737,16 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254513
254737
|
}
|
|
254514
254738
|
}
|
|
254515
254739
|
}));
|
|
254516
|
-
router.post("/:entityName", parseBody, withCollection(async (req, res, collection) => {
|
|
254740
|
+
router.post("/:entityName", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
|
|
254517
254741
|
const { appId, entityName } = req.params;
|
|
254518
254742
|
try {
|
|
254519
254743
|
const now = new Date().toISOString();
|
|
254520
|
-
const
|
|
254521
|
-
|
|
254522
|
-
|
|
254523
|
-
|
|
254524
|
-
|
|
254525
|
-
|
|
254526
|
-
created_date: now,
|
|
254527
|
-
updated_date: now
|
|
254528
|
-
};
|
|
254529
|
-
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");
|
|
254530
254750
|
emit(appId, entityName, "create", inserted);
|
|
254531
254751
|
res.status(201).json(inserted);
|
|
254532
254752
|
} catch (error48) {
|
|
@@ -254538,7 +254758,7 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254538
254758
|
res.status(500).json({ error: "Internal server error" });
|
|
254539
254759
|
}
|
|
254540
254760
|
}));
|
|
254541
|
-
router.post("/:entityName/bulk", parseBody, withCollection(async (req, res, collection) => {
|
|
254761
|
+
router.post("/:entityName/bulk", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
|
|
254542
254762
|
const { appId, entityName } = req.params;
|
|
254543
254763
|
if (!Array.isArray(req.body)) {
|
|
254544
254764
|
res.status(400).json({ error: "Request body must be an array" });
|
|
@@ -254547,17 +254767,15 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254547
254767
|
try {
|
|
254548
254768
|
const now = new Date().toISOString();
|
|
254549
254769
|
const records = [];
|
|
254550
|
-
for (const
|
|
254551
|
-
const
|
|
254552
|
-
|
|
254553
|
-
|
|
254554
|
-
|
|
254555
|
-
|
|
254556
|
-
|
|
254557
|
-
updated_date: now
|
|
254558
|
-
});
|
|
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);
|
|
254559
254777
|
}
|
|
254560
|
-
const inserted = stripInternalFields(await collection.insertAsync(records));
|
|
254778
|
+
const inserted = applyFLS(stripInternalFields(await collection.insertAsync(records)), schema10, currentUser, "read");
|
|
254561
254779
|
emit(appId, entityName, "create", inserted);
|
|
254562
254780
|
res.status(201).json(inserted);
|
|
254563
254781
|
} catch (error48) {
|
|
@@ -254569,11 +254787,24 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254569
254787
|
res.status(500).json({ error: "Internal server error" });
|
|
254570
254788
|
}
|
|
254571
254789
|
}));
|
|
254572
|
-
router.put("/:entityName/:id", parseBody, withCollection(async (req, res, collection) => {
|
|
254790
|
+
router.put("/:entityName/:id", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
|
|
254573
254791
|
const { appId, entityName, id: id2 } = req.params;
|
|
254574
254792
|
const { id: _id, created_date: _created_date, ...body } = req.body;
|
|
254575
254793
|
try {
|
|
254576
|
-
|
|
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");
|
|
254577
254808
|
db2.validate(entityName, filteredBody, true);
|
|
254578
254809
|
const updateData = {
|
|
254579
254810
|
...filteredBody,
|
|
@@ -254584,7 +254815,7 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254584
254815
|
res.status(404).json({ error: `Record with id "${id2}" not found` });
|
|
254585
254816
|
return;
|
|
254586
254817
|
}
|
|
254587
|
-
const updated = stripInternalFields(result.affectedDocuments);
|
|
254818
|
+
const updated = applyFLS(stripInternalFields(result.affectedDocuments), schema10, currentUser, "read");
|
|
254588
254819
|
emit(appId, entityName, "update", updated);
|
|
254589
254820
|
res.json(updated);
|
|
254590
254821
|
} catch (error48) {
|
|
@@ -254596,30 +254827,48 @@ async function createEntityRoutes(db2, logger2, broadcast) {
|
|
|
254596
254827
|
res.status(500).json({ error: "Internal server error" });
|
|
254597
254828
|
}
|
|
254598
254829
|
}));
|
|
254599
|
-
router.delete("/:entityName/:id", withCollection(async (req, res, collection) => {
|
|
254830
|
+
router.delete("/:entityName/:id", withCollection(async (req, res, collection, schema10, currentUser) => {
|
|
254600
254831
|
const { appId, entityName, id: id2 } = req.params;
|
|
254601
254832
|
try {
|
|
254602
254833
|
const doc2 = await collection.findOneAsync({ id: id2 });
|
|
254603
|
-
|
|
254604
|
-
if (numRemoved === 0) {
|
|
254834
|
+
if (!doc2) {
|
|
254605
254835
|
res.status(404).json({ error: `Record with id "${id2}" not found` });
|
|
254606
254836
|
return;
|
|
254607
254837
|
}
|
|
254608
|
-
if (doc2) {
|
|
254609
|
-
|
|
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;
|
|
254610
254843
|
}
|
|
254844
|
+
await collection.removeAsync({ id: id2 }, { multi: false });
|
|
254845
|
+
emit(appId, entityName, "delete", stripInternalFields(doc2));
|
|
254611
254846
|
res.json({ success: true });
|
|
254612
254847
|
} catch (error48) {
|
|
254613
254848
|
logger2.error(`Error in DELETE /${entityName}/${id2}:`, error48);
|
|
254614
254849
|
res.status(500).json({ error: "Internal server error" });
|
|
254615
254850
|
}
|
|
254616
254851
|
}));
|
|
254617
|
-
router.delete("/:entityName", parseBody, withCollection(async (req, res, collection) => {
|
|
254852
|
+
router.delete("/:entityName", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
|
|
254618
254853
|
const { entityName } = req.params;
|
|
254619
254854
|
try {
|
|
254620
254855
|
const query = req.body || {};
|
|
254621
|
-
const
|
|
254622
|
-
|
|
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
|
+
}
|
|
254623
254872
|
} catch (error48) {
|
|
254624
254873
|
logger2.error(`Error in DELETE /${entityName}:`, error48);
|
|
254625
254874
|
res.status(500).json({ error: "Internal server error" });
|
|
@@ -256419,7 +256668,9 @@ var DEFAULT_PORT = 4400;
|
|
|
256419
256668
|
var BASE44_APP_URL = "https://base44.app";
|
|
256420
256669
|
async function createDevServer(options8) {
|
|
256421
256670
|
const { port: userPort } = options8;
|
|
256422
|
-
const port = userPort ?? await getPorts({
|
|
256671
|
+
const port = userPort ?? await getPorts({
|
|
256672
|
+
port: process.env.IS_TEST === "true" ? undefined : DEFAULT_PORT
|
|
256673
|
+
});
|
|
256423
256674
|
const baseUrl = `http://localhost:${port}`;
|
|
256424
256675
|
const { functions, entities, project: project2 } = await options8.loadResources();
|
|
256425
256676
|
const app = import_express6.default();
|
|
@@ -256512,7 +256763,7 @@ async function createDevServer(options8) {
|
|
|
256512
256763
|
const { functions: functions2, entities: entities2 } = await options8.loadResources();
|
|
256513
256764
|
if (name2 === "functions") {
|
|
256514
256765
|
const previousFunctionCount = functionManager.getFunctionNames().length;
|
|
256515
|
-
functionManager.reload(functions2);
|
|
256766
|
+
await functionManager.reload(functions2);
|
|
256516
256767
|
const names = functionManager.getFunctionNames();
|
|
256517
256768
|
if (names.length > 0) {
|
|
256518
256769
|
devLogger.log(`Reloaded functions: ${names.sort().join(", ")}`);
|
|
@@ -256537,10 +256788,10 @@ async function createDevServer(options8) {
|
|
|
256537
256788
|
}
|
|
256538
256789
|
});
|
|
256539
256790
|
await base44ConfigWatcher.start();
|
|
256540
|
-
const shutdown = () => {
|
|
256791
|
+
const shutdown = async () => {
|
|
256541
256792
|
base44ConfigWatcher.close();
|
|
256542
256793
|
io6.close();
|
|
256543
|
-
functionManager.stopAll();
|
|
256794
|
+
await functionManager.stopAll();
|
|
256544
256795
|
server.close();
|
|
256545
256796
|
};
|
|
256546
256797
|
process.on("SIGINT", shutdown);
|
|
@@ -261031,4 +261282,4 @@ export {
|
|
|
261031
261282
|
CLIExitError
|
|
261032
261283
|
};
|
|
261033
261284
|
|
|
261034
|
-
//# debugId=
|
|
261285
|
+
//# debugId=827C7659385FB39764756E2164756E21
|