@rebasepro/cli 0.10.0 → 0.10.1-canary.0a881d4
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/README.md +1 -1
- package/dist/bundle.d.ts +146 -0
- package/dist/commands/apps.d.ts +1 -0
- package/dist/commands/build.d.ts +1 -1
- package/dist/commands/cloud/bundle-deploy.d.ts +53 -0
- package/dist/commands/cloud/context.d.ts +28 -0
- package/dist/commands/cloud/deployments.d.ts +16 -0
- package/dist/commands/cloud/env.d.ts +2 -0
- package/dist/commands/cloud/resources.d.ts +38 -0
- package/dist/commands/generate_sdk.d.ts +12 -0
- package/dist/commands/start.d.ts +1 -1
- package/dist/fold-static.d.ts +46 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.es.js +2567 -100
- package/dist/index.es.js.map +1 -1
- package/dist/manifest.d.ts +83 -0
- package/dist/utils/package-manager.d.ts +26 -2
- package/dist/utils/project.d.ts +11 -3
- package/package.json +8 -7
- package/runtime/dev-server.mjs +43 -0
- package/templates/overlays/baas/backend/src/index.ts +28 -4
- package/templates/overlays/baas/backend/src/storage.ts +71 -0
- package/templates/overlays/baas/rebase.json +14 -0
- package/templates/template/.env.example +16 -3
- package/templates/template/README.md +39 -29
- package/templates/template/backend/src/index.ts +28 -4
- package/templates/template/config/admin.d.ts +9 -0
- package/templates/template/config/collections/authors.ts +11 -9
- package/templates/template/config/collections/posts.ts +6 -4
- package/templates/template/config/collections/presets/ecommerce/categories.ts +7 -5
- package/templates/template/config/collections/presets/ecommerce/orders.ts +32 -20
- package/templates/template/config/collections/presets/ecommerce/products.ts +21 -13
- package/templates/template/config/collections/tags.ts +5 -3
- package/templates/template/config/collections/users.ts +34 -29
- package/templates/template/config/frontend-assets.d.ts +17 -0
- package/templates/template/config/index.ts +6 -0
- package/templates/template/config/package.json +26 -25
- package/templates/template/config/storage.ts +88 -0
- package/templates/template/gitignore +4 -0
- package/templates/template/rebase.json +20 -0
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PostgresCollectionConfig } from "@rebasepro/types";
|
|
2
2
|
|
|
3
|
-
const categoriesCollection:
|
|
3
|
+
const categoriesCollection: PostgresCollectionConfig = {
|
|
4
4
|
name: "Categories",
|
|
5
5
|
singularName: "Category",
|
|
6
6
|
slug: "categories",
|
|
7
7
|
table: "categories",
|
|
8
|
-
icon: "Category",
|
|
9
8
|
properties: {
|
|
10
9
|
id: {
|
|
11
10
|
name: "ID",
|
|
@@ -25,7 +24,7 @@ const categoriesCollection: CollectionConfig = {
|
|
|
25
24
|
description: {
|
|
26
25
|
name: "Description",
|
|
27
26
|
type: "string",
|
|
28
|
-
|
|
27
|
+
admin: {
|
|
29
28
|
multiline: true
|
|
30
29
|
}
|
|
31
30
|
},
|
|
@@ -34,7 +33,10 @@ const categoriesCollection: CollectionConfig = {
|
|
|
34
33
|
type: "string"
|
|
35
34
|
}
|
|
36
35
|
},
|
|
37
|
-
|
|
36
|
+
admin: {
|
|
37
|
+
icon: "Category",
|
|
38
|
+
propertiesOrder: ["id", "name", "slug", "description", "icon"]
|
|
39
|
+
}
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
export default categoriesCollection;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PostgresCollectionConfig } from "@rebasepro/types";
|
|
2
2
|
|
|
3
|
-
const ordersCollection:
|
|
3
|
+
const ordersCollection: PostgresCollectionConfig = {
|
|
4
4
|
name: "Orders",
|
|
5
5
|
singularName: "Order",
|
|
6
6
|
slug: "orders",
|
|
7
7
|
table: "orders",
|
|
8
|
-
icon: "Receipt",
|
|
9
8
|
properties: {
|
|
10
9
|
id: {
|
|
11
10
|
name: "ID",
|
|
@@ -22,21 +21,31 @@ const ordersCollection: CollectionConfig = {
|
|
|
22
21
|
name: "Status",
|
|
23
22
|
type: "string",
|
|
24
23
|
enum: [
|
|
25
|
-
{
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
label: "
|
|
33
|
-
color: "
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
{
|
|
25
|
+
id: "pending",
|
|
26
|
+
label: "Pending",
|
|
27
|
+
color: "gray"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "processing",
|
|
31
|
+
label: "Processing",
|
|
32
|
+
color: "blue"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "shipped",
|
|
36
|
+
label: "Shipped",
|
|
37
|
+
color: "orange"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "delivered",
|
|
41
|
+
label: "Delivered",
|
|
42
|
+
color: "green"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "cancelled",
|
|
46
|
+
label: "Cancelled",
|
|
47
|
+
color: "red"
|
|
48
|
+
}
|
|
40
49
|
]
|
|
41
50
|
},
|
|
42
51
|
total: {
|
|
@@ -47,7 +56,7 @@ color: "red" }
|
|
|
47
56
|
notes: {
|
|
48
57
|
name: "Notes",
|
|
49
58
|
type: "string",
|
|
50
|
-
|
|
59
|
+
admin: {
|
|
51
60
|
multiline: true
|
|
52
61
|
}
|
|
53
62
|
},
|
|
@@ -56,10 +65,13 @@ color: "red" }
|
|
|
56
65
|
type: "date",
|
|
57
66
|
columnName: "created_at",
|
|
58
67
|
autoValue: "on_create",
|
|
59
|
-
|
|
68
|
+
admin: {
|
|
60
69
|
readOnly: true
|
|
61
70
|
}
|
|
62
71
|
}
|
|
72
|
+
},
|
|
73
|
+
admin: {
|
|
74
|
+
icon: "Receipt"
|
|
63
75
|
}
|
|
64
76
|
};
|
|
65
77
|
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PostgresCollectionConfig } from "@rebasepro/types";
|
|
2
2
|
import categoriesCollection from "./categories.js";
|
|
3
3
|
|
|
4
|
-
const productsCollection:
|
|
4
|
+
const productsCollection: PostgresCollectionConfig = {
|
|
5
5
|
name: "Products",
|
|
6
6
|
singularName: "Product",
|
|
7
7
|
slug: "products",
|
|
8
8
|
table: "products",
|
|
9
|
-
icon: "ShoppingCart",
|
|
10
9
|
properties: {
|
|
11
10
|
id: {
|
|
12
11
|
name: "ID",
|
|
@@ -21,7 +20,7 @@ const productsCollection: CollectionConfig = {
|
|
|
21
20
|
description: {
|
|
22
21
|
name: "Description",
|
|
23
22
|
type: "string",
|
|
24
|
-
|
|
23
|
+
admin: {
|
|
25
24
|
markdown: true
|
|
26
25
|
}
|
|
27
26
|
},
|
|
@@ -39,15 +38,21 @@ const productsCollection: CollectionConfig = {
|
|
|
39
38
|
name: "Status",
|
|
40
39
|
type: "string",
|
|
41
40
|
enum: [
|
|
42
|
-
{
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
label: "
|
|
50
|
-
color: "
|
|
41
|
+
{
|
|
42
|
+
id: "draft",
|
|
43
|
+
label: "Draft",
|
|
44
|
+
color: "gray"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "active",
|
|
48
|
+
label: "Active",
|
|
49
|
+
color: "green"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "archived",
|
|
53
|
+
label: "Archived",
|
|
54
|
+
color: "orange"
|
|
55
|
+
}
|
|
51
56
|
]
|
|
52
57
|
},
|
|
53
58
|
category: {
|
|
@@ -58,6 +63,9 @@ color: "orange" }
|
|
|
58
63
|
cardinality: "one",
|
|
59
64
|
direction: "owning"
|
|
60
65
|
}
|
|
66
|
+
},
|
|
67
|
+
admin: {
|
|
68
|
+
icon: "ShoppingCart"
|
|
61
69
|
}
|
|
62
70
|
};
|
|
63
71
|
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PostgresCollectionConfig } from "@rebasepro/types";
|
|
2
2
|
|
|
3
|
-
const tagsCollection:
|
|
3
|
+
const tagsCollection: PostgresCollectionConfig = {
|
|
4
4
|
name: "Tags",
|
|
5
5
|
singularName: "Tag",
|
|
6
6
|
slug: "tags",
|
|
7
7
|
table: "tags",
|
|
8
|
-
icon: "Tag",
|
|
9
8
|
properties: {
|
|
10
9
|
id: {
|
|
11
10
|
name: "ID",
|
|
@@ -19,6 +18,9 @@ const tagsCollection: CollectionConfig = {
|
|
|
19
18
|
required: true
|
|
20
19
|
}
|
|
21
20
|
}
|
|
21
|
+
},
|
|
22
|
+
admin: {
|
|
23
|
+
icon: "Tag"
|
|
22
24
|
}
|
|
23
25
|
};
|
|
24
26
|
|
|
@@ -1,29 +1,28 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PostgresCollectionConfig } from "@rebasepro/types";
|
|
2
2
|
|
|
3
|
-
const usersCollection:
|
|
3
|
+
const usersCollection: PostgresCollectionConfig = {
|
|
4
4
|
name: "Users",
|
|
5
5
|
singularName: "User",
|
|
6
6
|
slug: "users",
|
|
7
7
|
auth: { enabled: true },
|
|
8
8
|
table: "users",
|
|
9
9
|
schema: "rebase",
|
|
10
|
-
icon: "Users",
|
|
11
|
-
group: "Settings",
|
|
12
|
-
openEntityMode: "dialog",
|
|
13
|
-
disableDefaultActions: ["copy"],
|
|
14
10
|
securityRules: [
|
|
15
|
-
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
{
|
|
12
|
+
operation: "select",
|
|
13
|
+
roles: ["admin"]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
operations: ["insert", "update", "delete"],
|
|
17
|
+
roles: ["admin"]
|
|
18
|
+
}
|
|
19
19
|
],
|
|
20
|
-
sort: ["createdAt", "desc"],
|
|
21
20
|
properties: {
|
|
22
21
|
id: {
|
|
23
22
|
name: "ID",
|
|
24
23
|
type: "string",
|
|
25
24
|
isId: "uuid",
|
|
26
|
-
|
|
25
|
+
admin: {
|
|
27
26
|
readOnly: true
|
|
28
27
|
}
|
|
29
28
|
},
|
|
@@ -44,9 +43,8 @@ roles: ["admin"] }
|
|
|
44
43
|
name: "Photo URL",
|
|
45
44
|
type: "string",
|
|
46
45
|
columnName: "photo_url",
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
46
|
+
url: true,
|
|
47
|
+
admin: { urlPreview: "image" }
|
|
50
48
|
},
|
|
51
49
|
roles: {
|
|
52
50
|
name: "Roles",
|
|
@@ -67,7 +65,7 @@ roles: ["admin"] }
|
|
|
67
65
|
type: "string",
|
|
68
66
|
columnName: "password_hash",
|
|
69
67
|
excludeFromApi: true,
|
|
70
|
-
|
|
68
|
+
admin: {
|
|
71
69
|
hideFromCollection: true,
|
|
72
70
|
disabled: { hidden: true }
|
|
73
71
|
}
|
|
@@ -77,7 +75,7 @@ roles: ["admin"] }
|
|
|
77
75
|
type: "boolean",
|
|
78
76
|
columnName: "email_verified",
|
|
79
77
|
defaultValue: false,
|
|
80
|
-
|
|
78
|
+
admin: {
|
|
81
79
|
hideFromCollection: true,
|
|
82
80
|
disabled: { hidden: true }
|
|
83
81
|
}
|
|
@@ -87,7 +85,7 @@ roles: ["admin"] }
|
|
|
87
85
|
type: "string",
|
|
88
86
|
columnName: "email_verification_token",
|
|
89
87
|
excludeFromApi: true,
|
|
90
|
-
|
|
88
|
+
admin: {
|
|
91
89
|
hideFromCollection: true,
|
|
92
90
|
disabled: { hidden: true }
|
|
93
91
|
}
|
|
@@ -96,7 +94,7 @@ roles: ["admin"] }
|
|
|
96
94
|
name: "Email Verification Sent At",
|
|
97
95
|
type: "date",
|
|
98
96
|
columnName: "email_verification_sent_at",
|
|
99
|
-
|
|
97
|
+
admin: {
|
|
100
98
|
hideFromCollection: true,
|
|
101
99
|
disabled: { hidden: true }
|
|
102
100
|
}
|
|
@@ -107,7 +105,7 @@ roles: ["admin"] }
|
|
|
107
105
|
keyValue: true,
|
|
108
106
|
properties: {},
|
|
109
107
|
defaultValue: {},
|
|
110
|
-
|
|
108
|
+
admin: {
|
|
111
109
|
hideFromCollection: true,
|
|
112
110
|
disabled: { hidden: true }
|
|
113
111
|
}
|
|
@@ -117,7 +115,7 @@ roles: ["admin"] }
|
|
|
117
115
|
type: "date",
|
|
118
116
|
columnName: "created_at",
|
|
119
117
|
autoValue: "on_create",
|
|
120
|
-
|
|
118
|
+
admin: {
|
|
121
119
|
readOnly: true
|
|
122
120
|
}
|
|
123
121
|
},
|
|
@@ -126,19 +124,26 @@ roles: ["admin"] }
|
|
|
126
124
|
type: "date",
|
|
127
125
|
columnName: "updated_at",
|
|
128
126
|
autoValue: "on_update",
|
|
129
|
-
|
|
127
|
+
admin: {
|
|
130
128
|
hideFromCollection: true,
|
|
131
129
|
disabled: { hidden: true }
|
|
132
130
|
}
|
|
133
131
|
}
|
|
134
132
|
},
|
|
135
|
-
|
|
136
|
-
"
|
|
137
|
-
"
|
|
138
|
-
"
|
|
139
|
-
"
|
|
140
|
-
"createdAt"
|
|
141
|
-
|
|
133
|
+
admin: {
|
|
134
|
+
icon: "Users",
|
|
135
|
+
group: "Settings",
|
|
136
|
+
openEntityMode: "dialog",
|
|
137
|
+
disableDefaultActions: ["copy"],
|
|
138
|
+
sort: ["createdAt", "desc"],
|
|
139
|
+
propertiesOrder: [
|
|
140
|
+
"id",
|
|
141
|
+
"email",
|
|
142
|
+
"displayName",
|
|
143
|
+
"roles",
|
|
144
|
+
"createdAt"
|
|
145
|
+
]
|
|
146
|
+
}
|
|
142
147
|
};
|
|
143
148
|
|
|
144
149
|
export default usersCollection;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ambient declarations for the asset imports a frontend component makes.
|
|
3
|
+
*
|
|
4
|
+
* A collection that points at a custom `Field` or `Preview` with a lazy `import()`
|
|
5
|
+
* thunk gets that reference type-checked — which means TypeScript resolves the
|
|
6
|
+
* component's *own* imports too, including `import icon from "./icon.png"`. That
|
|
7
|
+
* import is Vite's doing and means nothing to `tsc`: the frontend gets away with it
|
|
8
|
+
* through `vite/client` types, but those belong to the frontend's program, not this
|
|
9
|
+
* one, and Vite is not a dependency here (nothing in `config/` runs in a browser).
|
|
10
|
+
*
|
|
11
|
+
* Without these three lines, adding your first custom component fails the config and
|
|
12
|
+
* backend builds with `Cannot find module './icon.png'` — pointing at a frontend file
|
|
13
|
+
* from a build that has no obvious business compiling it.
|
|
14
|
+
*/
|
|
15
|
+
declare module "*.png" { const src: string; export default src; }
|
|
16
|
+
declare module "*.svg" { const src: string; export default src; }
|
|
17
|
+
declare module "*.jpg" { const src: string; export default src; }
|
|
@@ -4,3 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export { collections } from "./collections/index.js";
|
|
7
|
+
|
|
8
|
+
// Who may read, write, delete and list files in storage. Exported from here
|
|
9
|
+
// because that is where the runtime and `rebase cloud deploy` look for it — the
|
|
10
|
+
// bundle manifest records whether this export exists, so a managed deploy can be
|
|
11
|
+
// refused with a readable message instead of turning into a boot crash-loop.
|
|
12
|
+
export { storageAuthorize } from "./storage.js";
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
"name": "{{PROJECT_NAME}}-config",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared collections for frontend and backend",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"private": true,
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc --watch",
|
|
12
|
+
"clean": "rm -rf dist"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@rebasepro/admin-types": "workspace:*",
|
|
16
|
+
"@rebasepro/types": "workspace:*"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"typescript": "^5.9.2"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
13
26
|
},
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
},
|
|
17
|
-
"devDependencies": {
|
|
18
|
-
"typescript": "^5.9.2"
|
|
19
|
-
},
|
|
20
|
-
"exports": {
|
|
21
|
-
".": {
|
|
22
|
-
"import": "./dist/index.js",
|
|
23
|
-
"types": "./dist/index.d.ts",
|
|
24
|
-
"default": "./dist/index.js"
|
|
25
|
-
},
|
|
26
|
-
"./package.json": "./package.json"
|
|
27
|
-
}
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
}
|
|
28
29
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { isPublicStoragePath, type StorageAuthorize } from "@rebasepro/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Who may do what to files in storage.
|
|
5
|
+
*
|
|
6
|
+
* Storage is **not** under row-level security. Collections are: a request runs as
|
|
7
|
+
* `rebase_user` and Postgres decides row by row. Object storage has no equivalent,
|
|
8
|
+
* so keys share one flat namespace and this hook is the whole authorization model.
|
|
9
|
+
* Without it the server refuses to boot in production, because "authenticated"
|
|
10
|
+
* would be the only thing standing between a signed-in visitor and every file in
|
|
11
|
+
* the bucket — they could `GET /storage/list?prefix=` to enumerate every key, then
|
|
12
|
+
* read, overwrite or delete any of them.
|
|
13
|
+
*
|
|
14
|
+
* ## The model this starter ships
|
|
15
|
+
*
|
|
16
|
+
* A CMS's files are **shared content**, not private per-user documents: hero
|
|
17
|
+
* images, author pictures, product photos. The collections in this template upload
|
|
18
|
+
* to `author_pictures/`, `posts/hero/`, `product_images/` — paths named after the
|
|
19
|
+
* content, not after a user. So the boundary that matters here is not *whose file
|
|
20
|
+
* is this* but *who is allowed to change the library*:
|
|
21
|
+
*
|
|
22
|
+
* | operation | who |
|
|
23
|
+
* | --- | --- |
|
|
24
|
+
* | `read` of `public/…` | anyone, signed in or not |
|
|
25
|
+
* | `read` | any signed-in user — it is content, and the admin panel renders it |
|
|
26
|
+
* | `write`, `delete`, `list` | `admin` or `editor` only |
|
|
27
|
+
*
|
|
28
|
+
* Gating `list` is the important half. Enumeration is what turns "keys are hard to
|
|
29
|
+
* guess" into "keys are trivially known", and it is the first step of the attack
|
|
30
|
+
* the boot guard exists to prevent.
|
|
31
|
+
*
|
|
32
|
+
* ## If your app is multi-tenant, replace this
|
|
33
|
+
*
|
|
34
|
+
* Per-user or per-tenant files need an **ownership** check, and ownership lives in
|
|
35
|
+
* a row rather than in a key — so `ctx.data` hands you trusted, RLS-bypassing read
|
|
36
|
+
* access to answer it:
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* export const storageAuthorize: StorageAuthorize = async ({ key, user, operation, data }) => {
|
|
40
|
+
* if (operation === "read" && isPublicStoragePath(key)) return true;
|
|
41
|
+
* if (!user) return false;
|
|
42
|
+
*
|
|
43
|
+
* // Writes land under the caller's own prefix.
|
|
44
|
+
* if (operation === "write") return key.startsWith(`users/${user.uid}/`);
|
|
45
|
+
* if (operation === "list") return key.startsWith(`users/${user.uid}/`);
|
|
46
|
+
*
|
|
47
|
+
* // Reads and deletes check the row that owns the object.
|
|
48
|
+
* const attachment = (await data?.collection("attachments").find({
|
|
49
|
+
* where: { storage_key: ["==", key] }, limit: 1
|
|
50
|
+
* }))?.data?.[0];
|
|
51
|
+
* return attachment?.owner_id === user.uid;
|
|
52
|
+
* };
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* Note that a prefix rule alone is not an access-control model for reads — it only
|
|
56
|
+
* works because a write *puts* the object there. Anything already in the bucket
|
|
57
|
+
* needs the row consulted.
|
|
58
|
+
*
|
|
59
|
+
* The two escape hatches, if you would rather not write a hook at all:
|
|
60
|
+
* `storagePublicRead: true` when the bucket genuinely is a public read-only CDN,
|
|
61
|
+
* or `storageInsecureAllowAnyAuthenticated: true` for a single-tenant app where
|
|
62
|
+
* every signed-in user is trusted with every file. Both are set on
|
|
63
|
+
* `initializeRebaseBackend` in `backend/src/index.ts`.
|
|
64
|
+
*/
|
|
65
|
+
export const storageAuthorize: StorageAuthorize = ({ key, user, operation }) => {
|
|
66
|
+
// The `public/` prefix is the framework's convention for world-readable
|
|
67
|
+
// objects, and it is what signed download URLs for public assets rely on.
|
|
68
|
+
if (operation === "read" && isPublicStoragePath(key)) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Everything else needs a caller. `user` is null on routes that allow
|
|
73
|
+
// unauthenticated access, so this is the anonymous denial.
|
|
74
|
+
if (!user) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Content is shared: any signed-in user may read it. A `viewer` has to be able
|
|
79
|
+
// to see the images the admin panel shows them.
|
|
80
|
+
if (operation === "read") {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Changing or enumerating the library is an editor's job. Matches the roles in
|
|
85
|
+
// `collections/users.ts` — widen this if you add your own.
|
|
86
|
+
const roles = user.roles ?? [];
|
|
87
|
+
return roles.includes("admin") || roles.includes("editor");
|
|
88
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://rebase.pro/schemas/rebase.json",
|
|
3
|
+
"runtime": "^1",
|
|
4
|
+
"apps": {
|
|
5
|
+
"backend": {
|
|
6
|
+
"type": "backend"
|
|
7
|
+
},
|
|
8
|
+
"web": {
|
|
9
|
+
"type": "static",
|
|
10
|
+
"root": "frontend",
|
|
11
|
+
"build": "npm run build --workspace frontend",
|
|
12
|
+
"output": "frontend/dist",
|
|
13
|
+
"spa": true
|
|
14
|
+
},
|
|
15
|
+
"admin": {
|
|
16
|
+
"type": "admin",
|
|
17
|
+
"mode": "hosted"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|