@entity-access/entity-access 1.0.2 → 1.0.5
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/.github/workflows/node.yml +7 -2
- package/.vscode/settings.json +1 -0
- package/README.md +57 -27
- package/package.json +2 -2
- package/src/common/EntityAccessError.ts +10 -0
- package/src/common/IDisposable.ts +25 -0
- package/src/common/ImmutableObject.ts +53 -0
- package/src/common/Logger.ts +59 -0
- package/src/common/TypeInfo.ts +3 -0
- package/src/common/usingAsync.ts +42 -12
- package/src/compiler/QueryCompiler.ts +28 -30
- package/src/compiler/postgres/PostgreSqlMethodTransformer.ts +23 -0
- package/src/compiler/sql-server/SqlServerSqlMethodTransformer.ts +23 -0
- package/src/decorators/ForeignKey.ts +1 -1
- package/src/decorators/IClassOf.ts +2 -1
- package/src/decorators/parser/NameParser.ts +15 -0
- package/src/di/di.ts +224 -0
- package/src/drivers/base/BaseDriver.ts +28 -3
- package/src/drivers/sql-server/ExpressionToSqlServer.ts +48 -9
- package/src/drivers/sql-server/SqlServerDriver.ts +7 -16
- package/src/entity-query/EntityType.ts +1 -1
- package/src/model/EntityContext.ts +167 -22
- package/src/model/EntityQuery.ts +76 -60
- package/src/model/EntitySource.ts +39 -33
- package/src/model/IFilterWithParameter.ts +3 -0
- package/src/model/SourceExpression.ts +21 -25
- package/src/model/{ChangeEntry.ts → changes/ChangeEntry.ts} +33 -3
- package/src/model/{ChangeSet.ts → changes/ChangeSet.ts} +12 -11
- package/src/model/events/ContextEvents.ts +26 -0
- package/src/model/events/EntityEvents.ts +92 -0
- package/src/model/{IdentityService.ts → identity/IdentityService.ts} +1 -1
- package/src/model/symbols.ts +1 -0
- package/src/model/verification/VerificationSession.ts +173 -0
- package/src/query/ast/DebugStringVisitor.ts +128 -0
- package/src/query/ast/ExpressionToSql.ts +289 -119
- package/src/query/ast/Expressions.ts +111 -12
- package/src/query/ast/IStringTransformer.ts +16 -4
- package/src/query/ast/ReplaceParameter.ts +40 -0
- package/src/query/ast/Visitor.ts +20 -5
- package/src/query/parser/ArrowToExpression.ts +116 -16
- package/src/query/parser/BabelVisitor.ts +27 -44
- package/src/query/parser/NotSupportedError.ts +5 -0
- package/src/query/parser/Restructure.ts +66 -0
- package/src/query/parser/TransformVisitor.ts +83 -0
- package/src/sql/ISql.ts +10 -0
- package/src/tests/db-tests/tests/select-items.ts +12 -0
- package/src/tests/expressions/left-joins/child-joins.ts +19 -26
- package/src/tests/expressions/sanitize/sanitize-test.ts +17 -0
- package/src/tests/expressions/select/select.ts +24 -0
- package/src/tests/expressions/simple/parse-arrow.ts +10 -0
- package/src/tests/model/ShoppingContext.ts +7 -3
- package/src/tests/model/createContext.ts +45 -13
- package/src/tests/security/ShoppingContextEvents.ts +20 -0
- package/src/tests/security/events/OrderEvents.ts +66 -0
- package/src/tests/security/events/ProductEvents.ts +92 -0
- package/src/tests/security/events/UserEvents.ts +18 -0
- package/src/tests/security/events/UserInfo.ts +7 -0
- package/src/tests/security/tests/place-order.ts +71 -0
- package/test.js +11 -4
- package/tsconfig.json +2 -0
- package/src/decorators/parser/MemberParser.ts +0 -8
- package/src/model/EntitySchema.ts +0 -21
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import EntityAccessError from "../../../common/EntityAccessError.js";
|
|
2
|
+
import Inject from "../../../di/di.js";
|
|
3
|
+
import { IEntityQuery } from "../../../model/IFilterWithParameter.js";
|
|
4
|
+
import EntityEvents from "../../../model/events/EntityEvents.js";
|
|
5
|
+
import { Product, ProductCategory, ProductPrice } from "../../model/ShoppingContext.js";
|
|
6
|
+
import { UserInfo } from "./UserInfo.js";
|
|
7
|
+
const statusPublished = "published";
|
|
8
|
+
|
|
9
|
+
export class ProductEvents extends EntityEvents<Product> {
|
|
10
|
+
|
|
11
|
+
@Inject
|
|
12
|
+
user: UserInfo;
|
|
13
|
+
|
|
14
|
+
filter(query: IEntityQuery<Product>): IEntityQuery<Product> {
|
|
15
|
+
const { userID } = this.user;
|
|
16
|
+
|
|
17
|
+
// admin can access everything so return null
|
|
18
|
+
if (this.user.admin) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
// everyone can read published or own products
|
|
22
|
+
return query.where({ userID, statusPublished }, (p) => (x) => x.ownerID === p.userID || x.status === p.statusPublished);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
modify(query: IEntityQuery<Product>): IEntityQuery<Product> {
|
|
26
|
+
const { userID } = this.user;
|
|
27
|
+
|
|
28
|
+
// admin can access everything so return null
|
|
29
|
+
if (this.user.admin) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// customer can modify its own products
|
|
34
|
+
return query.where({ userID }, (p) => (x) => x.ownerID === p.userID);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class ProductCategoryEvents extends EntityEvents<ProductCategory> {
|
|
40
|
+
|
|
41
|
+
@Inject
|
|
42
|
+
user: UserInfo;
|
|
43
|
+
|
|
44
|
+
filter(query: IEntityQuery<ProductCategory>): IEntityQuery<ProductCategory> {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
modify(query: IEntityQuery<ProductCategory>): IEntityQuery<ProductCategory> {
|
|
49
|
+
const { userID } = this.user;
|
|
50
|
+
|
|
51
|
+
// admin can access everything so return null
|
|
52
|
+
if (this.user.admin) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
EntityAccessError.throw("Access denied");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
export class ProductPriceEvents extends EntityEvents<ProductPrice> {
|
|
63
|
+
|
|
64
|
+
@Inject
|
|
65
|
+
user: UserInfo;
|
|
66
|
+
|
|
67
|
+
filter(query: IEntityQuery<ProductPrice>): IEntityQuery<ProductPrice> {
|
|
68
|
+
|
|
69
|
+
const { userID } = this.user;
|
|
70
|
+
|
|
71
|
+
// admin can access everything so return null
|
|
72
|
+
if (this.user.admin) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// user can view prices of only published or own products
|
|
77
|
+
return query.where({ userID, statusPublished } , (p) => (x) => x.product.status === p.statusPublished || x.product.ownerID === p.userID);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
modify(query: IEntityQuery<ProductPrice>): IEntityQuery<ProductPrice> {
|
|
81
|
+
const { userID } = this.user;
|
|
82
|
+
|
|
83
|
+
// admin can access everything so return null
|
|
84
|
+
if (this.user.admin) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// user can only edit its own prices
|
|
89
|
+
return query.where({ userID }, (p) => (x) => x.product.ownerID === p.userID);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Inject from "../../../di/di.js";
|
|
2
|
+
import { IEntityQuery } from "../../../model/IFilterWithParameter.js";
|
|
3
|
+
import EntityEvents from "../../../model/events/EntityEvents.js";
|
|
4
|
+
import { User } from "../../model/ShoppingContext.js";
|
|
5
|
+
import { UserInfo } from "./UserInfo.js";
|
|
6
|
+
export class UserEvents extends EntityEvents<User> {
|
|
7
|
+
|
|
8
|
+
@Inject
|
|
9
|
+
user: UserInfo;
|
|
10
|
+
|
|
11
|
+
filter(query: IEntityQuery<User>): IEntityQuery<User> {
|
|
12
|
+
if (this.user.admin) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const { userID } = this.user;
|
|
16
|
+
return query.where({ userID }, (p) => (x) => x.userID === p.userID);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import assert from "assert";
|
|
2
|
+
import Logger from "../../../common/Logger.js";
|
|
3
|
+
import { ServiceProvider } from "../../../di/di.js";
|
|
4
|
+
import { BaseDriver } from "../../../drivers/base/BaseDriver.js";
|
|
5
|
+
import ContextEvents from "../../../model/events/ContextEvents.js";
|
|
6
|
+
import { TestConfig } from "../../TestConfig.js";
|
|
7
|
+
import { ShoppingContext, User } from "../../model/ShoppingContext.js";
|
|
8
|
+
import { createContext } from "../../model/createContext.js";
|
|
9
|
+
import { ShoppingContextEvents } from "../ShoppingContextEvents.js";
|
|
10
|
+
import { UserInfo } from "../events/UserInfo.js";
|
|
11
|
+
|
|
12
|
+
export default async function(this: TestConfig) {
|
|
13
|
+
|
|
14
|
+
const customer = await createUser(this);
|
|
15
|
+
|
|
16
|
+
await addNewOrder.call(this, customer);
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
await addNewOrder.call(this, customer, 1);
|
|
20
|
+
assert.fail("No error thrown");
|
|
21
|
+
} catch(error) {
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async function addNewOrder(this: TestConfig, customer: User, userID?) {
|
|
28
|
+
const scope = ServiceProvider.global.createScope();
|
|
29
|
+
try {
|
|
30
|
+
const user = new UserInfo();
|
|
31
|
+
user.userID = userID ?? customer.userID;
|
|
32
|
+
scope.add(Logger, Logger.instance);
|
|
33
|
+
scope.add(BaseDriver, this.driver);
|
|
34
|
+
scope.add(UserInfo, user);
|
|
35
|
+
scope.add(ContextEvents, new ShoppingContextEvents());
|
|
36
|
+
const context = scope.create(ShoppingContext);
|
|
37
|
+
context.verifyFilters = true;
|
|
38
|
+
|
|
39
|
+
// get first headphone...
|
|
40
|
+
const headPhone = await context.products.all().firstOrFail();
|
|
41
|
+
const headPhonePrice = await context.productPrices.where({ id: headPhone.productID }, (p) => (x) => x.productID === p.id).firstOrFail();
|
|
42
|
+
|
|
43
|
+
context.orders.add({
|
|
44
|
+
customer,
|
|
45
|
+
orderDate: new Date(),
|
|
46
|
+
orderItems: [
|
|
47
|
+
context.orderItems.add({
|
|
48
|
+
product: headPhone,
|
|
49
|
+
productPrice: headPhonePrice,
|
|
50
|
+
amount: headPhonePrice.amount,
|
|
51
|
+
})
|
|
52
|
+
]
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await context.saveChanges();
|
|
56
|
+
|
|
57
|
+
} finally {
|
|
58
|
+
scope.dispose();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function createUser(config: TestConfig) {
|
|
63
|
+
const context = await createContext(config.driver);
|
|
64
|
+
config.driver.connectionString.database = context.driver.connectionString.database;
|
|
65
|
+
const user = context.users.add({
|
|
66
|
+
dateCreated: new Date()
|
|
67
|
+
});
|
|
68
|
+
await context.saveChanges();
|
|
69
|
+
return user;
|
|
70
|
+
}
|
|
71
|
+
|
package/test.js
CHANGED
|
@@ -40,10 +40,8 @@ export default class TestRunner {
|
|
|
40
40
|
options: {
|
|
41
41
|
encrypt: true, // for azure
|
|
42
42
|
trustServerCertificate: true // change to true for local dev / self-signed certs
|
|
43
|
-
}
|
|
44
|
-
debug: true
|
|
43
|
+
}
|
|
45
44
|
})
|
|
46
|
-
|
|
47
45
|
];
|
|
48
46
|
}
|
|
49
47
|
|
|
@@ -89,14 +87,17 @@ export default class TestRunner {
|
|
|
89
87
|
|
|
90
88
|
}
|
|
91
89
|
|
|
90
|
+
const testDb = !process.argv.includes("no-db");
|
|
92
91
|
|
|
93
|
-
await TestRunner.runAll("./dist/tests",
|
|
92
|
+
await TestRunner.runAll("./dist/tests", testDb);
|
|
94
93
|
|
|
95
94
|
let exitCode = 0;
|
|
95
|
+
let failed = 0;
|
|
96
96
|
|
|
97
97
|
for (const { error, name } of results) {
|
|
98
98
|
if (error) {
|
|
99
99
|
exitCode = 1;
|
|
100
|
+
failed++;
|
|
100
101
|
console.error(`${name} failed`);
|
|
101
102
|
console.error(error?.stack ?? error);
|
|
102
103
|
continue;
|
|
@@ -104,4 +105,10 @@ for (const { error, name } of results) {
|
|
|
104
105
|
console.log(`${name} executed.`);
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
if (exitCode === 0) {
|
|
109
|
+
console.log(`${results.length} tests ran successfully.`);
|
|
110
|
+
} else {
|
|
111
|
+
console.log(`${failed} Tests out of ${results.length} failed.`);
|
|
112
|
+
}
|
|
113
|
+
|
|
107
114
|
process.exit(exitCode);
|
package/tsconfig.json
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "ESNext",
|
|
4
4
|
"module":"NodeNext",
|
|
5
|
+
"incremental": true,
|
|
5
6
|
"sourceMap": true,
|
|
6
7
|
"declaration": true,
|
|
7
8
|
"declarationMap": true,
|
|
8
9
|
"outDir": "dist",
|
|
10
|
+
"skipDefaultLibCheck": true,
|
|
9
11
|
"experimentalDecorators": true,
|
|
10
12
|
"emitDecoratorMetadata": true,
|
|
11
13
|
"lib": [
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export class EntityColumn {
|
|
2
|
-
constructor(
|
|
3
|
-
public readonly name: string,
|
|
4
|
-
public readonly columnName: string,
|
|
5
|
-
public readonly storageType: string
|
|
6
|
-
) {
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export default class EntitySchema {
|
|
12
|
-
|
|
13
|
-
constructor(
|
|
14
|
-
public readonly name: string,
|
|
15
|
-
public readonly table: string,
|
|
16
|
-
public readonly columns: EntityColumn[],
|
|
17
|
-
public readonly schema?: string,
|
|
18
|
-
) {
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
}
|