@rebasepro/common 0.12.0 → 0.12.1-canary.g06f263c
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/index.es.js +66 -9
- package/dist/index.es.js.map +1 -1
- package/dist/util/auth-default-policies.d.ts +18 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/policy/sqlToPolicy.d.ts +6 -1
- package/dist/util/string-column-length.d.ts +24 -0
- package/package.json +7 -7
- package/src/util/auth-default-policies.ts +22 -0
- package/src/util/index.ts +1 -0
- package/src/util/pg-column-to-property.ts +14 -1
- package/src/util/policy/evaluatePolicy.ts +6 -2
- package/src/util/policy/policyToPostgres.ts +9 -3
- package/src/util/policy/sqlToPolicy.ts +10 -3
- package/src/util/string-column-length.ts +31 -0
package/dist/index.es.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ANONYMOUS_USER_ID, CANONICAL_TO_REST, DEFAULT_DATA_SOURCE_KEY, EntityReference, EntityRelation, NULL_OPS, REST_TO_CANONICAL, getDataSourceCapabilities, getDeclaredSubcollections, isManyToMany, isPostgresCollectionConfig, isRelationalCollectionConfig, policy, toCanonicalOp } from "@rebasepro/types";
|
|
2
|
-
import { deepClone, generateForeignKeyName, getIn, getPolicyOperations, isDefaultFieldConfigId, mergeDeep, prettifyIdentifier, randomString, removeFunctions, toSnakeCase } from "@rebasepro/utils";
|
|
1
|
+
import { ANONYMOUS_USER_ID, ANONYMOUS_USER_IDS, CANONICAL_TO_REST, DEFAULT_DATA_SOURCE_KEY, EntityReference, EntityRelation, NULL_OPS, REST_TO_CANONICAL, getDataSourceCapabilities, getDeclaredSubcollections, isAnonymousUid, isManyToMany, isPostgresCollectionConfig, isRelationalCollectionConfig, policy, toCanonicalOp } from "@rebasepro/types";
|
|
2
|
+
import { deepClone, generateForeignKeyName, getIn, getPolicyNamesForRules, getPolicyOperations, isDefaultFieldConfigId, mergeDeep, prettifyIdentifier, randomString, removeFunctions, toSnakeCase } from "@rebasepro/utils";
|
|
3
3
|
import jsonLogic from "json-logic-js";
|
|
4
4
|
import { deepEqual } from "fast-equals";
|
|
5
5
|
//#region src/util/common.ts
|
|
@@ -957,7 +957,12 @@ var UID_NOT_NULL = /auth\.uid\(\)\s+IS\s+NOT\s+NULL/i;
|
|
|
957
957
|
* is how the trusted *server* context is recognised), so:
|
|
958
958
|
*
|
|
959
959
|
* - `auth.uid() IS NOT NULL` is a tautology on the user path, and
|
|
960
|
-
* - `auth.uid() != 'anon'`
|
|
960
|
+
* - `auth.uid() != 'anon'` excludes one spelling of anonymous and admits the
|
|
961
|
+
* other. This one is not hypothetical and was not only a foreign habit:
|
|
962
|
+
* rebase's own request path reported `'anon'` while everything that compiled
|
|
963
|
+
* or checked a policy used `'anonymous'`, so whichever literal an author
|
|
964
|
+
* picked, half the anonymous callers walked through. See
|
|
965
|
+
* {@link ANONYMOUS_USER_IDS}.
|
|
961
966
|
*
|
|
962
967
|
* Either one turns a lockdown into a full grant, and neither looks wrong. No
|
|
963
968
|
* real user id is ever one of these literals, and a user-context request is
|
|
@@ -995,7 +1000,7 @@ function findAnonymousGrants(expr) {
|
|
|
995
1000
|
found.push({
|
|
996
1001
|
pattern: "foreign-uid-literal",
|
|
997
1002
|
detail: literal.value,
|
|
998
|
-
explanation: `'${literal.value}' is a ${platform} convention. Rebase reports an anonymous request as '${ANONYMOUS_USER_ID}', so comparing against '${literal.value}' passes for every caller. Use \`condition: policy.authenticated()\` to mean "signed in".`
|
|
1003
|
+
explanation: `'${literal.value}' is a ${platform} convention. Rebase reports an anonymous request as '${ANONYMOUS_USER_ID}', so comparing against '${literal.value}' passes for every caller. Use \`condition: policy.authenticated()\` to mean "signed in" — it compiles to NOT IN (${ANONYMOUS_USER_IDS.map((v) => `'${v}'`).join(", ")}), covering every spelling rebase has reported rather than whichever one you remember.`
|
|
999
1004
|
});
|
|
1000
1005
|
return;
|
|
1001
1006
|
}
|
|
@@ -1090,7 +1095,7 @@ function compile(expr, scope) {
|
|
|
1090
1095
|
}
|
|
1091
1096
|
case "rolesOverlap": return `string_to_array(auth.roles(), ',') && ${rolesArraySql(expr.roles)}`;
|
|
1092
1097
|
case "rolesContain": return `string_to_array(auth.roles(), ',') @> ${rolesArraySql(expr.roles)}`;
|
|
1093
|
-
case "authenticated": return `auth.uid() IS NOT NULL AND auth.uid()
|
|
1098
|
+
case "authenticated": return `auth.uid() IS NOT NULL AND auth.uid() NOT IN (${ANONYMOUS_USER_IDS.map(quoteLiteral).join(", ")})`;
|
|
1094
1099
|
case "serverContext": return "auth.uid() IS NULL";
|
|
1095
1100
|
case "existsIn": return compileExistsIn(expr, scope);
|
|
1096
1101
|
case "raw": return expr.sql.replace(/\{(\w+)\}/g, (_, col) => `${outerQualifier(scope)}${resolveColumnName(col, scope.outerCollection)}`);
|
|
@@ -1187,7 +1192,7 @@ function evaluatePolicy(expr, ctx) {
|
|
|
1187
1192
|
const userRoles = ctx.roles ?? [];
|
|
1188
1193
|
return expr.roles.every((r) => r === "public" || userRoles.includes(r));
|
|
1189
1194
|
}
|
|
1190
|
-
case "authenticated": return ctx.uid != null && ctx.uid
|
|
1195
|
+
case "authenticated": return ctx.uid != null && !isAnonymousUid(ctx.uid);
|
|
1191
1196
|
case "serverContext": return false;
|
|
1192
1197
|
case "existsIn": return "unknown";
|
|
1193
1198
|
case "raw": return "unknown";
|
|
@@ -1653,6 +1658,26 @@ function getInjectedSecurityRules(collection) {
|
|
|
1653
1658
|
const explicitCount = (collection.securityRules ?? []).length;
|
|
1654
1659
|
return getEffectiveSecurityRules(collection).slice(explicitCount);
|
|
1655
1660
|
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Every policy name `rebase db push` would write for a collection.
|
|
1663
|
+
*
|
|
1664
|
+
* This is the answer to "did the codebase produce this live policy?", and it is
|
|
1665
|
+
* more than `securityRules.map(r => r.name)` for two reasons:
|
|
1666
|
+
*
|
|
1667
|
+
* - a rule without an explicit `name` compiles to `<table>_<op>_<hash>`, one
|
|
1668
|
+
* per operation, so comparing `rule.name` to `policyname` never matches it;
|
|
1669
|
+
* - the generator also injects the safe-by-default baseline
|
|
1670
|
+
* (`<table>_default_admin_*`), which is in no collection's `securityRules`.
|
|
1671
|
+
*
|
|
1672
|
+
* Every UI that flags drift has to get both right, and each one that derived it
|
|
1673
|
+
* by hand got a different subset — which is how four policies *Rebase itself
|
|
1674
|
+
* wrote* came to be badged as hand-written drift on every table in a project,
|
|
1675
|
+
* with a button offering to import them back into the codebase that produced
|
|
1676
|
+
* them. There is one derivation now, and this is it.
|
|
1677
|
+
*/
|
|
1678
|
+
function getGeneratedPolicyNames(collection) {
|
|
1679
|
+
return getPolicyNamesForRules(getEffectiveSecurityRules(collection), getTableName(collection));
|
|
1680
|
+
}
|
|
1656
1681
|
//#endregion
|
|
1657
1682
|
//#region src/util/junction-policies.ts
|
|
1658
1683
|
var SERVER_OR_ADMIN_EXPR = policy.or(policy.serverContext(), policy.rolesOverlap(["admin"]));
|
|
@@ -1952,7 +1977,7 @@ function buildConditionContext(params) {
|
|
|
1952
1977
|
* Maps a PostgreSQL column data type to a Rebase property type.
|
|
1953
1978
|
*/
|
|
1954
1979
|
function pgTypeToRebaseProperty(column) {
|
|
1955
|
-
const { column_name, data_type, udt_name, is_nullable, column_default, enum_values } = column;
|
|
1980
|
+
const { column_name, data_type, udt_name, is_nullable, column_default, character_maximum_length, enum_values } = column;
|
|
1956
1981
|
const required = is_nullable === "NO";
|
|
1957
1982
|
const prettifiedName = prettifyIdentifier(column_name);
|
|
1958
1983
|
const isAutoId = column_default != null && (column_default.includes("nextval") || column_default.includes("gen_random_uuid") || column_default.includes("uuid_generate") || column_default.includes("identity"));
|
|
@@ -1976,11 +2001,15 @@ function pgTypeToRebaseProperty(column) {
|
|
|
1976
2001
|
let colType = "varchar";
|
|
1977
2002
|
if (dt === "text" || dt === "citext") colType = "text";
|
|
1978
2003
|
if (dt === "char" || dt === "character") colType = "char";
|
|
2004
|
+
const declaredLength = colType === "text" ? null : character_maximum_length;
|
|
1979
2005
|
const prop = {
|
|
1980
2006
|
type: "string",
|
|
1981
2007
|
name: prettifiedName,
|
|
1982
2008
|
columnType: colType,
|
|
1983
|
-
validation: required ? {
|
|
2009
|
+
validation: required || declaredLength ? {
|
|
2010
|
+
...required ? { required: true } : {},
|
|
2011
|
+
...declaredLength ? { max: declaredLength } : {}
|
|
2012
|
+
} : void 0
|
|
1984
2013
|
};
|
|
1985
2014
|
if (isAutoId) prop.isId = "manual";
|
|
1986
2015
|
return prop;
|
|
@@ -2184,6 +2213,34 @@ function buildCollectionFromTableMetadata(tableName, metadata) {
|
|
|
2184
2213
|
};
|
|
2185
2214
|
}
|
|
2186
2215
|
//#endregion
|
|
2216
|
+
//#region src/util/string-column-length.ts
|
|
2217
|
+
/**
|
|
2218
|
+
* The length a bounded string column is declared with when the property does
|
|
2219
|
+
* not say. Historical: it is what the DDL generator hardcoded, kept so that
|
|
2220
|
+
* regenerating an existing schema does not silently redefine its columns.
|
|
2221
|
+
*/
|
|
2222
|
+
var DEFAULT_STRING_COLUMN_LENGTH = 255;
|
|
2223
|
+
/**
|
|
2224
|
+
* How wide a `varchar`/`char` column should be for a given property.
|
|
2225
|
+
*
|
|
2226
|
+
* One definition, three call sites, because they used to disagree. For the same
|
|
2227
|
+
* `columnType: "varchar"` property the DDL generator emitted `VARCHAR(255)`
|
|
2228
|
+
* while the Drizzle generator emitted a bare `varchar("col")` — which Postgres
|
|
2229
|
+
* reads as *unbounded* — so which of the two you ran decided whether the column
|
|
2230
|
+
* had a limit at all. Introspection then dropped the length entirely, so reading
|
|
2231
|
+
* an existing `character varying(500)` column back and regenerating it produced
|
|
2232
|
+
* a `VARCHAR(255)`: a silent narrowing of a column with data already in it.
|
|
2233
|
+
*
|
|
2234
|
+
* `validation.max` is the property's own statement about how long the value may
|
|
2235
|
+
* be, so it is the only sensible source for the column's width — and it keeps
|
|
2236
|
+
* the constraint the database enforces in step with the one the app enforces,
|
|
2237
|
+
* rather than inventing a second, different limit underneath it.
|
|
2238
|
+
*/
|
|
2239
|
+
function resolveStringColumnLength(prop) {
|
|
2240
|
+
const max = prop.validation?.max;
|
|
2241
|
+
return typeof max === "number" && Number.isInteger(max) && max > 0 ? max : 255;
|
|
2242
|
+
}
|
|
2243
|
+
//#endregion
|
|
2187
2244
|
//#region src/data/resolveDataSource.ts
|
|
2188
2245
|
/**
|
|
2189
2246
|
* Build a keyed registry from a list of {@link DataSourceDefinition}s.
|
|
@@ -3773,6 +3830,6 @@ async function detectJunctionTables(executeSql) {
|
|
|
3773
3830
|
return junctionTables;
|
|
3774
3831
|
}
|
|
3775
3832
|
//#endregion
|
|
3776
|
-
export { COLLECTION_PATH_SEPARATOR, COMPOSITE_ID_SEPARATOR, CollectionRegistry, DEFAULT_FIND_ALL_MAX_ROWS, DEFAULT_MAX_PAGES, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, DEFAULT_PAGE_SIZE, JUNCTION_TABLES_SQL, QueryBuilder, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, RebasePaginationError, and, buildCollection, buildCollectionFromTableMetadata, buildCompositeId, buildConditionContext, buildProperty, buildPropertyCallbacks, buildRebaseData, buildRoutedRebaseData, buildSdkData, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, checkOperation, classifyTable, collectAllPages, cond, createDataSourceRegistry, createPaginationHelpers, createRelationRef, createRelationRefWithData, defaultUsersCollection, defineCollection, deserializeFilter, deserializeLogicalCondition, deserializeOrderBy, detectJunctionTables, embedParentExpression, enumToObjectEntries, evaluateCondition, evaluatePolicy, findAnonymousGrants, findRelation, fullPathToCollectionSegments, getArrayResolvedProperties, getColumnName, getDeclaredPrimaryKeys, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEffectiveSecurityRules, getEntityChildViews, getEnumVarName, getInjectedSecurityRules, getJunctionCollectionConfig, getJunctionSecurityRules, getLabelOrConfigFrom, getPrimaryKeys, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, getTableVarName, isJunctionBackedRelation, isPropertyBuilder, isRebaseInternalTable, normalizeToEntityRelation, or, paginateFind, parseIdValues, policyToPostgres, registerConditionOperations, resolveArrayProperties, resolveCollectionRelations, resolveDataSource, resolveEnumValues, resolveJunctionSpecs, resolvePrimaryKeys, resolveProperties, resolveProperty, resolvePropertyEnum, resolveRelation, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, sanitizeData, securityRuleToConditions, segmentsToStrippedPath, serializeFilter, serializeLogicalCondition, serializeOrderBy, sortProperties, sqlToPolicy, stripCollectionPath, traverseValueProperty, traverseValuesProperties, updateDateAutoValues, wrapAsEntityData, wrapAsSdkData };
|
|
3833
|
+
export { COLLECTION_PATH_SEPARATOR, COMPOSITE_ID_SEPARATOR, CollectionRegistry, DEFAULT_FIND_ALL_MAX_ROWS, DEFAULT_MAX_PAGES, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, DEFAULT_PAGE_SIZE, DEFAULT_STRING_COLUMN_LENGTH, JUNCTION_TABLES_SQL, QueryBuilder, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, RebasePaginationError, and, buildCollection, buildCollectionFromTableMetadata, buildCompositeId, buildConditionContext, buildProperty, buildPropertyCallbacks, buildRebaseData, buildRoutedRebaseData, buildSdkData, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, checkOperation, classifyTable, collectAllPages, cond, createDataSourceRegistry, createPaginationHelpers, createRelationRef, createRelationRefWithData, defaultUsersCollection, defineCollection, deserializeFilter, deserializeLogicalCondition, deserializeOrderBy, detectJunctionTables, embedParentExpression, enumToObjectEntries, evaluateCondition, evaluatePolicy, findAnonymousGrants, findRelation, fullPathToCollectionSegments, getArrayResolvedProperties, getColumnName, getDeclaredPrimaryKeys, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEffectiveSecurityRules, getEntityChildViews, getEnumVarName, getGeneratedPolicyNames, getInjectedSecurityRules, getJunctionCollectionConfig, getJunctionSecurityRules, getLabelOrConfigFrom, getPrimaryKeys, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, getTableVarName, isJunctionBackedRelation, isPropertyBuilder, isRebaseInternalTable, normalizeToEntityRelation, or, paginateFind, parseIdValues, policyToPostgres, registerConditionOperations, resolveArrayProperties, resolveCollectionRelations, resolveDataSource, resolveEnumValues, resolveJunctionSpecs, resolvePrimaryKeys, resolveProperties, resolveProperty, resolvePropertyEnum, resolveRelation, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, resolveStringColumnLength, sanitizeData, securityRuleToConditions, segmentsToStrippedPath, serializeFilter, serializeLogicalCondition, serializeOrderBy, sortProperties, sqlToPolicy, stripCollectionPath, traverseValueProperty, traverseValuesProperties, updateDateAutoValues, wrapAsEntityData, wrapAsSdkData };
|
|
3777
3834
|
|
|
3778
3835
|
//# sourceMappingURL=index.es.js.map
|