@mccaskie-labs/workbench-config 0.2.0 → 0.2.1
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/CHANGELOG.md +6 -0
- package/capabilities/database.json +2 -71
- package/capabilities/schema.json +34 -2
- package/capabilities/sign-in.json +137 -0
- package/dist/capabilities.gen.d.ts +95 -47
- package/dist/capabilities.gen.js +140 -72
- package/dist/capability-schema.d.ts +27 -1
- package/dist/capability-schema.js +30 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @mccaskie-labs/workbench-config
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Sign-in gets its own component, `sign-in`: a built-in skill (`workbench-sign-in`) that `workbench-start` lists, with one client question (which pages and actions need a signed-in person, who among them may use each, and which also need a fresh code), written to `workbench/sign-in.json`. Its surfaces, config (`SESSION_SECRET`, `TURNSTILE_SITE_KEY`, `TURNSTILE_SECRET_KEY`), Turnstile actions and three rules move out of `database` (rule ids now `sign-in.server-side`, `sign-in.writes-same-origin`, `sign-in.checks-for-bots`); the probes keep their `database.*` ids. A new optional `proved_by` lets a manifest's rules name probes in the components it lists (sign-in names `database`); only then may its own `probes` be empty.
|
|
8
|
+
|
|
3
9
|
## 0.2.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
|
@@ -17,10 +17,7 @@
|
|
|
17
17
|
"createServerClient",
|
|
18
18
|
"createUserClient",
|
|
19
19
|
"createServiceClient",
|
|
20
|
-
"WorkbenchSupabaseError"
|
|
21
|
-
"createSessions",
|
|
22
|
-
"safeReturnPath",
|
|
23
|
-
"sameOrigin"
|
|
20
|
+
"WorkbenchSupabaseError"
|
|
24
21
|
],
|
|
25
22
|
"tools": [
|
|
26
23
|
"sb_project_create",
|
|
@@ -29,9 +26,7 @@
|
|
|
29
26
|
"sb_query",
|
|
30
27
|
"sb_project_delete",
|
|
31
28
|
"cf_worker_secret_put",
|
|
32
|
-
"vendor_settings_apply"
|
|
33
|
-
"cf_turnstile_widget_create",
|
|
34
|
-
"cf_turnstile_widget_delete"
|
|
29
|
+
"vendor_settings_apply"
|
|
35
30
|
]
|
|
36
31
|
},
|
|
37
32
|
"config": [
|
|
@@ -66,39 +61,6 @@
|
|
|
66
61
|
"production"
|
|
67
62
|
],
|
|
68
63
|
"description": "The sb_secret_ key, only for code that must bypass RLS (createServiceClient); written by cf_worker_secret_put source=supabase_secret, always two-step"
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
"name": "SESSION_SECRET",
|
|
72
|
-
"required": false,
|
|
73
|
-
"secret": true,
|
|
74
|
-
"environments": [
|
|
75
|
-
"development",
|
|
76
|
-
"staging",
|
|
77
|
-
"production"
|
|
78
|
-
],
|
|
79
|
-
"description": "Seals the sign-in session cookie (createSessions). 32 random bytes written by cf_worker_secret_put source=generate:32; replacing it signs everybody out, so it is two-step. Only needed by an app with sign-in"
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
"name": "TURNSTILE_SITE_KEY",
|
|
83
|
-
"required": false,
|
|
84
|
-
"secret": false,
|
|
85
|
-
"environments": [
|
|
86
|
-
"development",
|
|
87
|
-
"staging",
|
|
88
|
-
"production"
|
|
89
|
-
],
|
|
90
|
-
"description": "The public site key of the hostname's Turnstile widget, for the sign-in bot check; written by cf_worker_secret_put source=turnstile_sitekey after cf_turnstile_widget_create. Outside development, sign-in won't send codes without it"
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
"name": "TURNSTILE_SECRET_KEY",
|
|
94
|
-
"required": false,
|
|
95
|
-
"secret": true,
|
|
96
|
-
"environments": [
|
|
97
|
-
"development",
|
|
98
|
-
"staging",
|
|
99
|
-
"production"
|
|
100
|
-
],
|
|
101
|
-
"description": "The secret of the hostname's Turnstile widget, sent only to Cloudflare's Siteverify; written by cf_worker_secret_put source=turnstile_secret. Outside development, sign-in won't send codes without it"
|
|
102
64
|
}
|
|
103
65
|
],
|
|
104
66
|
"actions": [
|
|
@@ -141,16 +103,6 @@
|
|
|
141
103
|
"gate": "two_step_when",
|
|
142
104
|
"when": "production, after the first successful apply on that database; staging and check_only are free",
|
|
143
105
|
"ships": "5"
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
"tool": "cf_turnstile_widget_create",
|
|
147
|
-
"gate": "free",
|
|
148
|
-
"ships": "5"
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
"tool": "cf_turnstile_widget_delete",
|
|
152
|
-
"gate": "two_step",
|
|
153
|
-
"ships": "5"
|
|
154
106
|
}
|
|
155
107
|
],
|
|
156
108
|
"rules": [
|
|
@@ -195,27 +147,6 @@
|
|
|
195
147
|
"text": "Keep Supabase's sign-in settings at the Workbench baseline (workbench-config VENDOR_SETTINGS), or record a project's change in vendor-settings.json with a reason. Control applies it with vendor_settings_apply and checks it every day.",
|
|
196
148
|
"enforced_by": "control",
|
|
197
149
|
"probe": "database.vendor-settings"
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
"id": "database.sign-in-server-side",
|
|
201
|
-
"kind": "must",
|
|
202
|
-
"text": "Sign people in through the app's own endpoints with createSessions: the browser never talks to Supabase Auth and never holds the access token, and codes are typed, never links.",
|
|
203
|
-
"enforced_by": "review",
|
|
204
|
-
"probe": "database.sign-in"
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
"id": "database.session-writes-same-origin",
|
|
208
|
-
"kind": "must",
|
|
209
|
-
"text": "Refuse a write (anything but GET or HEAD) made with the session cookie unless sameOrigin(request) is true.",
|
|
210
|
-
"enforced_by": "review",
|
|
211
|
-
"probe": "database.session-cookie"
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
"id": "database.sign-in-checks-for-bots",
|
|
215
|
-
"kind": "must",
|
|
216
|
-
"text": "Outside development, verify a Turnstile token with Siteverify (success, this hostname and the sign-in action) before asking Supabase to send or re-send a sign-in code, and send no code when either Turnstile key is missing.",
|
|
217
|
-
"enforced_by": "review",
|
|
218
|
-
"probe": "database.sign-in-bot-check"
|
|
219
150
|
}
|
|
220
151
|
],
|
|
221
152
|
"examples": [
|
package/capabilities/schema.json
CHANGED
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"github",
|
|
35
35
|
"strings",
|
|
36
36
|
"ui",
|
|
37
|
-
"forms"
|
|
37
|
+
"forms",
|
|
38
|
+
"sign-in"
|
|
38
39
|
]
|
|
39
40
|
},
|
|
40
41
|
"title": {
|
|
@@ -352,7 +353,6 @@
|
|
|
352
353
|
}
|
|
353
354
|
},
|
|
354
355
|
"probes": {
|
|
355
|
-
"minItems": 1,
|
|
356
356
|
"type": "array",
|
|
357
357
|
"items": {
|
|
358
358
|
"type": "object",
|
|
@@ -402,6 +402,38 @@
|
|
|
402
402
|
"additionalProperties": false
|
|
403
403
|
}
|
|
404
404
|
},
|
|
405
|
+
"proved_by": {
|
|
406
|
+
"minItems": 1,
|
|
407
|
+
"type": "array",
|
|
408
|
+
"items": {
|
|
409
|
+
"type": "string",
|
|
410
|
+
"enum": [
|
|
411
|
+
"gateway",
|
|
412
|
+
"ai",
|
|
413
|
+
"images",
|
|
414
|
+
"image-search",
|
|
415
|
+
"config",
|
|
416
|
+
"email",
|
|
417
|
+
"email-inbound",
|
|
418
|
+
"email-sending",
|
|
419
|
+
"database",
|
|
420
|
+
"workers",
|
|
421
|
+
"domains",
|
|
422
|
+
"dns",
|
|
423
|
+
"analytics",
|
|
424
|
+
"country",
|
|
425
|
+
"pdf",
|
|
426
|
+
"access",
|
|
427
|
+
"r2",
|
|
428
|
+
"kv",
|
|
429
|
+
"github",
|
|
430
|
+
"strings",
|
|
431
|
+
"ui",
|
|
432
|
+
"forms",
|
|
433
|
+
"sign-in"
|
|
434
|
+
]
|
|
435
|
+
}
|
|
436
|
+
},
|
|
405
437
|
"offer": {
|
|
406
438
|
"type": "object",
|
|
407
439
|
"properties": {
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./schema.json",
|
|
3
|
+
"format": 1,
|
|
4
|
+
"component": "sign-in",
|
|
5
|
+
"title": "Sign-in",
|
|
6
|
+
"summary": "People sign in to the app with a code emailed to them and typed on the next screen: no password, no link and no reset email. The Worker holds the session in one sealed cookie and talks to Supabase Auth itself, so the browser never sees a token, and a Cloudflare Turnstile check runs before any code is sent. The template has it all; a page that needs a signed-in person asks for one with requireUser. Signing in proves who someone is, not what they may do: anyone can make an account.",
|
|
7
|
+
"status": "built",
|
|
8
|
+
"delivery": {
|
|
9
|
+
"choice": "built_in",
|
|
10
|
+
"intro": "People signing in to the app with a code emailed to them, with no passwords to forget."
|
|
11
|
+
},
|
|
12
|
+
"surfaces": {
|
|
13
|
+
"package": [
|
|
14
|
+
"createSessions",
|
|
15
|
+
"safeReturnPath",
|
|
16
|
+
"sameOrigin",
|
|
17
|
+
"plainAuthError"
|
|
18
|
+
],
|
|
19
|
+
"tools": [
|
|
20
|
+
"cf_worker_secret_put",
|
|
21
|
+
"cf_turnstile_widget_create",
|
|
22
|
+
"cf_turnstile_widget_delete"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"config": [
|
|
26
|
+
{
|
|
27
|
+
"name": "SESSION_SECRET",
|
|
28
|
+
"required": false,
|
|
29
|
+
"secret": true,
|
|
30
|
+
"environments": [
|
|
31
|
+
"development",
|
|
32
|
+
"staging",
|
|
33
|
+
"production"
|
|
34
|
+
],
|
|
35
|
+
"description": "Seals the sign-in session cookie (createSessions). 32 random bytes written by cf_worker_secret_put source=generate:32; replacing it signs everybody out, so it is two-step. Only needed by an app with sign-in"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "TURNSTILE_SITE_KEY",
|
|
39
|
+
"required": false,
|
|
40
|
+
"secret": false,
|
|
41
|
+
"environments": [
|
|
42
|
+
"development",
|
|
43
|
+
"staging",
|
|
44
|
+
"production"
|
|
45
|
+
],
|
|
46
|
+
"description": "The public site key of the hostname's Turnstile widget, for the sign-in bot check; written by cf_worker_secret_put source=turnstile_sitekey after cf_turnstile_widget_create. Outside development, sign-in won't send codes without it"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "TURNSTILE_SECRET_KEY",
|
|
50
|
+
"required": false,
|
|
51
|
+
"secret": true,
|
|
52
|
+
"environments": [
|
|
53
|
+
"development",
|
|
54
|
+
"staging",
|
|
55
|
+
"production"
|
|
56
|
+
],
|
|
57
|
+
"description": "The secret of the hostname's Turnstile widget, sent only to Cloudflare's Siteverify; written by cf_worker_secret_put source=turnstile_secret. Outside development, sign-in won't send codes without it"
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"actions": [
|
|
61
|
+
{
|
|
62
|
+
"tool": "cf_turnstile_widget_create",
|
|
63
|
+
"gate": "free",
|
|
64
|
+
"ships": "5"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"tool": "cf_turnstile_widget_delete",
|
|
68
|
+
"gate": "two_step",
|
|
69
|
+
"ships": "5"
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"rules": [
|
|
73
|
+
{
|
|
74
|
+
"id": "sign-in.server-side",
|
|
75
|
+
"kind": "must",
|
|
76
|
+
"text": "Sign people in through the app's own endpoints with createSessions: the browser never talks to Supabase Auth and never holds the access token, and codes are typed, never links.",
|
|
77
|
+
"enforced_by": "review",
|
|
78
|
+
"probe": "database.sign-in"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "sign-in.writes-same-origin",
|
|
82
|
+
"kind": "must",
|
|
83
|
+
"text": "Refuse a write (anything but GET or HEAD) made with the session cookie unless sameOrigin(request) is true.",
|
|
84
|
+
"enforced_by": "review",
|
|
85
|
+
"probe": "database.session-cookie"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": "sign-in.checks-for-bots",
|
|
89
|
+
"kind": "must",
|
|
90
|
+
"text": "Outside development, verify a Turnstile token with Siteverify (success, this hostname and the sign-in action) before asking Supabase to send or re-send a sign-in code, and send no code when either Turnstile key is missing.",
|
|
91
|
+
"enforced_by": "review",
|
|
92
|
+
"probe": "database.sign-in-bot-check"
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"probes": [],
|
|
96
|
+
"offer": {
|
|
97
|
+
"pattern": "switch_on",
|
|
98
|
+
"when": "The client wants people to sign in or have accounts: a members' area, saved orders or bookings, a profile, \"my account\", or a page only some people may see.",
|
|
99
|
+
"say": "Sign-in is already built in: people get a code by email and type it, so there are no passwords to forget or leak. I'll put the pages that need it behind it. Here's what I think needs sign-in; tell me what to change.",
|
|
100
|
+
"answers_file": "workbench/sign-in.json",
|
|
101
|
+
"answers_format": "`{ \"format\": 1, \"status\": \"draft\" | \"done\", \"pages\": [{ \"path\": \"/orders\", \"what\": \"<what it shows or does, in the client's words>\", \"who\": \"signed_in\" | \"owner\" | \"admins\", \"fresh\": true | false }] }`, one entry per page or action that needs a signed-in person. `who` is `signed_in` (anyone with an account), `owner` (only the person the thing belongs to, enforced by row-level security on their user id through `dbAs`) or `admins` (only people in `app_admins`). `fresh` is true where it also needs a code typed in the last 15 minutes. Save it as a draft until the client accepts the table. Then build each page with `requireUser`, enforce its `who`, and check `fresh` on the ones marked so.",
|
|
102
|
+
"questions": [
|
|
103
|
+
{
|
|
104
|
+
"id": "sign-in.pages",
|
|
105
|
+
"sets": [
|
|
106
|
+
"page.sign_in",
|
|
107
|
+
"page.who",
|
|
108
|
+
"page.fresh"
|
|
109
|
+
],
|
|
110
|
+
"ask": "Here are the pages and actions that need someone signed in, who among them may use each one, and which also need a fresh code because they show or change something sensitive. Accept them, or change some?",
|
|
111
|
+
"style": "table",
|
|
112
|
+
"note": "Propose from the pages built or planned. Anything that shows or changes a person's own things (orders, bookings, a profile) needs sign-in, with `who` set to `owner`. Signing in only proves who someone is, and anyone can make an account, so a page for staff or admins must say `admins`: `requireUser` alone would let any stranger who signs up use it. Mark `fresh` where a stranger holding a signed-in phone could do harm: showing payment details, changing an email address, adding an admin, deleting an account. The template has no re-check screen: when `fresh` is false, the page tells the person to sign out and sign in again with a new code, as the admins section of `/admin/brand` does. Pages anyone may read, such as the home page, a menu or a contact form, stay open. Only the client knows what is private and who may do what, so always show the table."
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
"not_asked": [
|
|
116
|
+
{
|
|
117
|
+
"sets": "sign_in.method",
|
|
118
|
+
"why": "The template signs people in with an emailed code only (Workbench's choice: no password to forget, leak or reuse), and the authentication standard rules out sign-in links. Passwords and social sign-in (\"sign in with Google\") aren't built. If the client asks for one, say so and that it is the Workbench owner's call; don't build it."
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"sets": "sign_in.length",
|
|
122
|
+
"why": "Sessions last weeks, with a cap, as the authentication standard (§10) asks. The package's defaults (SESSION_DEFAULTS) sign a person out after 14 days without a visit and 30 days after the code was typed, whatever happens; on a shared device they untick \"keep me signed in\" and are signed out after 30 minutes idle. Say so if the client asks, and don't change them."
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"sets": "sign_in.who",
|
|
126
|
+
"why": "Anyone with an email address can make an account; invite-only sign-up isn't built yet. If the client needs only invited people, say it isn't built yet and ask them how they want to handle it. Don't build an invite system of your own."
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"sets": "sign_in.bot_check",
|
|
130
|
+
"why": "The Turnstile check is always on outside development. Control sets it up (cf_turnstile_widget_create, then cf_worker_secret_put sources turnstile_sitekey and turnstile_secret); there is nothing to ask."
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
"proved_by": [
|
|
135
|
+
"database"
|
|
136
|
+
]
|
|
137
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const MANIFEST_VERSION: "
|
|
1
|
+
export declare const MANIFEST_VERSION: "984099bc7d3f";
|
|
2
2
|
export declare const capabilities: {
|
|
3
3
|
readonly ai: {
|
|
4
4
|
readonly $schema: "./schema.json";
|
|
@@ -437,8 +437,8 @@ export declare const capabilities: {
|
|
|
437
437
|
readonly summary: "Each project has its own Supabase projects, <id>-staging and <id>-production. Apps use them natively through workbench-supabase; Control creates them, writes their URL and keys onto the Workers, and applies migrations read from the repo.";
|
|
438
438
|
readonly status: "built";
|
|
439
439
|
readonly surfaces: {
|
|
440
|
-
readonly tools: readonly ["sb_project_create", "sb_migration_apply", "sb_migration_rollback", "sb_query", "sb_project_delete", "cf_worker_secret_put", "vendor_settings_apply"
|
|
441
|
-
readonly package: readonly ["supabaseConfig", "createBrowserClient", "createServerClient", "createUserClient", "createServiceClient", "WorkbenchSupabaseError"
|
|
440
|
+
readonly tools: readonly ["sb_project_create", "sb_migration_apply", "sb_migration_rollback", "sb_query", "sb_project_delete", "cf_worker_secret_put", "vendor_settings_apply"];
|
|
441
|
+
readonly package: readonly ["supabaseConfig", "createBrowserClient", "createServerClient", "createUserClient", "createServiceClient", "WorkbenchSupabaseError"];
|
|
442
442
|
};
|
|
443
443
|
readonly config: readonly [{
|
|
444
444
|
readonly name: "SUPABASE_URL";
|
|
@@ -458,24 +458,6 @@ export declare const capabilities: {
|
|
|
458
458
|
readonly secret: true;
|
|
459
459
|
readonly environments: readonly ["staging", "production"];
|
|
460
460
|
readonly description: "The sb_secret_ key, only for code that must bypass RLS (createServiceClient); written by cf_worker_secret_put source=supabase_secret, always two-step";
|
|
461
|
-
}, {
|
|
462
|
-
readonly name: "SESSION_SECRET";
|
|
463
|
-
readonly required: false;
|
|
464
|
-
readonly secret: true;
|
|
465
|
-
readonly environments: readonly ["development", "staging", "production"];
|
|
466
|
-
readonly description: "Seals the sign-in session cookie (createSessions). 32 random bytes written by cf_worker_secret_put source=generate:32; replacing it signs everybody out, so it is two-step. Only needed by an app with sign-in";
|
|
467
|
-
}, {
|
|
468
|
-
readonly name: "TURNSTILE_SITE_KEY";
|
|
469
|
-
readonly required: false;
|
|
470
|
-
readonly secret: false;
|
|
471
|
-
readonly environments: readonly ["development", "staging", "production"];
|
|
472
|
-
readonly description: "The public site key of the hostname's Turnstile widget, for the sign-in bot check; written by cf_worker_secret_put source=turnstile_sitekey after cf_turnstile_widget_create. Outside development, sign-in won't send codes without it";
|
|
473
|
-
}, {
|
|
474
|
-
readonly name: "TURNSTILE_SECRET_KEY";
|
|
475
|
-
readonly required: false;
|
|
476
|
-
readonly secret: true;
|
|
477
|
-
readonly environments: readonly ["development", "staging", "production"];
|
|
478
|
-
readonly description: "The secret of the hostname's Turnstile widget, sent only to Cloudflare's Siteverify; written by cf_worker_secret_put source=turnstile_secret. Outside development, sign-in won't send codes without it";
|
|
479
461
|
}];
|
|
480
462
|
readonly actions: readonly [{
|
|
481
463
|
readonly tool: "sb_project_create";
|
|
@@ -510,14 +492,6 @@ export declare const capabilities: {
|
|
|
510
492
|
readonly gate: "two_step_when";
|
|
511
493
|
readonly when: "production, after the first successful apply on that database; staging and check_only are free";
|
|
512
494
|
readonly ships: "5";
|
|
513
|
-
}, {
|
|
514
|
-
readonly tool: "cf_turnstile_widget_create";
|
|
515
|
-
readonly gate: "free";
|
|
516
|
-
readonly ships: "5";
|
|
517
|
-
}, {
|
|
518
|
-
readonly tool: "cf_turnstile_widget_delete";
|
|
519
|
-
readonly gate: "two_step";
|
|
520
|
-
readonly ships: "5";
|
|
521
495
|
}];
|
|
522
496
|
readonly rules: readonly [{
|
|
523
497
|
readonly id: "database.rls-isolates-users";
|
|
@@ -555,24 +529,6 @@ export declare const capabilities: {
|
|
|
555
529
|
readonly text: "Keep Supabase's sign-in settings at the Workbench baseline (workbench-config VENDOR_SETTINGS), or record a project's change in vendor-settings.json with a reason. Control applies it with vendor_settings_apply and checks it every day.";
|
|
556
530
|
readonly enforced_by: "control";
|
|
557
531
|
readonly probe: "database.vendor-settings";
|
|
558
|
-
}, {
|
|
559
|
-
readonly id: "database.sign-in-server-side";
|
|
560
|
-
readonly kind: "must";
|
|
561
|
-
readonly text: "Sign people in through the app's own endpoints with createSessions: the browser never talks to Supabase Auth and never holds the access token, and codes are typed, never links.";
|
|
562
|
-
readonly enforced_by: "review";
|
|
563
|
-
readonly probe: "database.sign-in";
|
|
564
|
-
}, {
|
|
565
|
-
readonly id: "database.session-writes-same-origin";
|
|
566
|
-
readonly kind: "must";
|
|
567
|
-
readonly text: "Refuse a write (anything but GET or HEAD) made with the session cookie unless sameOrigin(request) is true.";
|
|
568
|
-
readonly enforced_by: "review";
|
|
569
|
-
readonly probe: "database.session-cookie";
|
|
570
|
-
}, {
|
|
571
|
-
readonly id: "database.sign-in-checks-for-bots";
|
|
572
|
-
readonly kind: "must";
|
|
573
|
-
readonly text: "Outside development, verify a Turnstile token with Siteverify (success, this hostname and the sign-in action) before asking Supabase to send or re-send a sign-in code, and send no code when either Turnstile key is missing.";
|
|
574
|
-
readonly enforced_by: "review";
|
|
575
|
-
readonly probe: "database.sign-in-bot-check";
|
|
576
532
|
}];
|
|
577
533
|
readonly examples: readonly [{
|
|
578
534
|
readonly id: "database.example.user-client";
|
|
@@ -1934,6 +1890,98 @@ export declare const capabilities: {
|
|
|
1934
1890
|
readonly when: "The client wants a PDF: an invoice, receipt, quote, certificate, report or anything else to download or print.";
|
|
1935
1891
|
};
|
|
1936
1892
|
};
|
|
1893
|
+
readonly "sign-in": {
|
|
1894
|
+
readonly $schema: "./schema.json";
|
|
1895
|
+
readonly format: 1;
|
|
1896
|
+
readonly component: "sign-in";
|
|
1897
|
+
readonly title: "Sign-in";
|
|
1898
|
+
readonly summary: "People sign in to the app with a code emailed to them and typed on the next screen: no password, no link and no reset email. The Worker holds the session in one sealed cookie and talks to Supabase Auth itself, so the browser never sees a token, and a Cloudflare Turnstile check runs before any code is sent. The template has it all; a page that needs a signed-in person asks for one with requireUser. Signing in proves who someone is, not what they may do: anyone can make an account.";
|
|
1899
|
+
readonly status: "built";
|
|
1900
|
+
readonly surfaces: {
|
|
1901
|
+
readonly tools: readonly ["cf_worker_secret_put", "cf_turnstile_widget_create", "cf_turnstile_widget_delete"];
|
|
1902
|
+
readonly package: readonly ["createSessions", "safeReturnPath", "sameOrigin", "plainAuthError"];
|
|
1903
|
+
};
|
|
1904
|
+
readonly config: readonly [{
|
|
1905
|
+
readonly name: "SESSION_SECRET";
|
|
1906
|
+
readonly required: false;
|
|
1907
|
+
readonly secret: true;
|
|
1908
|
+
readonly environments: readonly ["development", "staging", "production"];
|
|
1909
|
+
readonly description: "Seals the sign-in session cookie (createSessions). 32 random bytes written by cf_worker_secret_put source=generate:32; replacing it signs everybody out, so it is two-step. Only needed by an app with sign-in";
|
|
1910
|
+
}, {
|
|
1911
|
+
readonly name: "TURNSTILE_SITE_KEY";
|
|
1912
|
+
readonly required: false;
|
|
1913
|
+
readonly secret: false;
|
|
1914
|
+
readonly environments: readonly ["development", "staging", "production"];
|
|
1915
|
+
readonly description: "The public site key of the hostname's Turnstile widget, for the sign-in bot check; written by cf_worker_secret_put source=turnstile_sitekey after cf_turnstile_widget_create. Outside development, sign-in won't send codes without it";
|
|
1916
|
+
}, {
|
|
1917
|
+
readonly name: "TURNSTILE_SECRET_KEY";
|
|
1918
|
+
readonly required: false;
|
|
1919
|
+
readonly secret: true;
|
|
1920
|
+
readonly environments: readonly ["development", "staging", "production"];
|
|
1921
|
+
readonly description: "The secret of the hostname's Turnstile widget, sent only to Cloudflare's Siteverify; written by cf_worker_secret_put source=turnstile_secret. Outside development, sign-in won't send codes without it";
|
|
1922
|
+
}];
|
|
1923
|
+
readonly actions: readonly [{
|
|
1924
|
+
readonly tool: "cf_turnstile_widget_create";
|
|
1925
|
+
readonly gate: "free";
|
|
1926
|
+
readonly ships: "5";
|
|
1927
|
+
}, {
|
|
1928
|
+
readonly tool: "cf_turnstile_widget_delete";
|
|
1929
|
+
readonly gate: "two_step";
|
|
1930
|
+
readonly ships: "5";
|
|
1931
|
+
}];
|
|
1932
|
+
readonly rules: readonly [{
|
|
1933
|
+
readonly id: "sign-in.server-side";
|
|
1934
|
+
readonly kind: "must";
|
|
1935
|
+
readonly text: "Sign people in through the app's own endpoints with createSessions: the browser never talks to Supabase Auth and never holds the access token, and codes are typed, never links.";
|
|
1936
|
+
readonly enforced_by: "review";
|
|
1937
|
+
readonly probe: "database.sign-in";
|
|
1938
|
+
}, {
|
|
1939
|
+
readonly id: "sign-in.writes-same-origin";
|
|
1940
|
+
readonly kind: "must";
|
|
1941
|
+
readonly text: "Refuse a write (anything but GET or HEAD) made with the session cookie unless sameOrigin(request) is true.";
|
|
1942
|
+
readonly enforced_by: "review";
|
|
1943
|
+
readonly probe: "database.session-cookie";
|
|
1944
|
+
}, {
|
|
1945
|
+
readonly id: "sign-in.checks-for-bots";
|
|
1946
|
+
readonly kind: "must";
|
|
1947
|
+
readonly text: "Outside development, verify a Turnstile token with Siteverify (success, this hostname and the sign-in action) before asking Supabase to send or re-send a sign-in code, and send no code when either Turnstile key is missing.";
|
|
1948
|
+
readonly enforced_by: "review";
|
|
1949
|
+
readonly probe: "database.sign-in-bot-check";
|
|
1950
|
+
}];
|
|
1951
|
+
readonly probes: readonly [];
|
|
1952
|
+
readonly proved_by: readonly ["database"];
|
|
1953
|
+
readonly offer: {
|
|
1954
|
+
readonly pattern: "switch_on";
|
|
1955
|
+
readonly when: "The client wants people to sign in or have accounts: a members' area, saved orders or bookings, a profile, \"my account\", or a page only some people may see.";
|
|
1956
|
+
readonly say: "Sign-in is already built in: people get a code by email and type it, so there are no passwords to forget or leak. I'll put the pages that need it behind it. Here's what I think needs sign-in; tell me what to change.";
|
|
1957
|
+
readonly answers_file: "workbench/sign-in.json";
|
|
1958
|
+
readonly answers_format: "`{ \"format\": 1, \"status\": \"draft\" | \"done\", \"pages\": [{ \"path\": \"/orders\", \"what\": \"<what it shows or does, in the client's words>\", \"who\": \"signed_in\" | \"owner\" | \"admins\", \"fresh\": true | false }] }`, one entry per page or action that needs a signed-in person. `who` is `signed_in` (anyone with an account), `owner` (only the person the thing belongs to, enforced by row-level security on their user id through `dbAs`) or `admins` (only people in `app_admins`). `fresh` is true where it also needs a code typed in the last 15 minutes. Save it as a draft until the client accepts the table. Then build each page with `requireUser`, enforce its `who`, and check `fresh` on the ones marked so.";
|
|
1959
|
+
readonly questions: readonly [{
|
|
1960
|
+
readonly id: "sign-in.pages";
|
|
1961
|
+
readonly sets: readonly ["page.sign_in", "page.who", "page.fresh"];
|
|
1962
|
+
readonly ask: "Here are the pages and actions that need someone signed in, who among them may use each one, and which also need a fresh code because they show or change something sensitive. Accept them, or change some?";
|
|
1963
|
+
readonly style: "table";
|
|
1964
|
+
readonly note: "Propose from the pages built or planned. Anything that shows or changes a person's own things (orders, bookings, a profile) needs sign-in, with `who` set to `owner`. Signing in only proves who someone is, and anyone can make an account, so a page for staff or admins must say `admins`: `requireUser` alone would let any stranger who signs up use it. Mark `fresh` where a stranger holding a signed-in phone could do harm: showing payment details, changing an email address, adding an admin, deleting an account. The template has no re-check screen: when `fresh` is false, the page tells the person to sign out and sign in again with a new code, as the admins section of `/admin/brand` does. Pages anyone may read, such as the home page, a menu or a contact form, stay open. Only the client knows what is private and who may do what, so always show the table.";
|
|
1965
|
+
}];
|
|
1966
|
+
readonly not_asked: readonly [{
|
|
1967
|
+
readonly sets: "sign_in.method";
|
|
1968
|
+
readonly why: "The template signs people in with an emailed code only (Workbench's choice: no password to forget, leak or reuse), and the authentication standard rules out sign-in links. Passwords and social sign-in (\"sign in with Google\") aren't built. If the client asks for one, say so and that it is the Workbench owner's call; don't build it.";
|
|
1969
|
+
}, {
|
|
1970
|
+
readonly sets: "sign_in.length";
|
|
1971
|
+
readonly why: "Sessions last weeks, with a cap, as the authentication standard (§10) asks. The package's defaults (SESSION_DEFAULTS) sign a person out after 14 days without a visit and 30 days after the code was typed, whatever happens; on a shared device they untick \"keep me signed in\" and are signed out after 30 minutes idle. Say so if the client asks, and don't change them.";
|
|
1972
|
+
}, {
|
|
1973
|
+
readonly sets: "sign_in.who";
|
|
1974
|
+
readonly why: "Anyone with an email address can make an account; invite-only sign-up isn't built yet. If the client needs only invited people, say it isn't built yet and ask them how they want to handle it. Don't build an invite system of your own.";
|
|
1975
|
+
}, {
|
|
1976
|
+
readonly sets: "sign_in.bot_check";
|
|
1977
|
+
readonly why: "The Turnstile check is always on outside development. Control sets it up (cf_turnstile_widget_create, then cf_worker_secret_put sources turnstile_sitekey and turnstile_secret); there is nothing to ask.";
|
|
1978
|
+
}];
|
|
1979
|
+
};
|
|
1980
|
+
readonly delivery: {
|
|
1981
|
+
readonly choice: "built_in";
|
|
1982
|
+
readonly intro: "People signing in to the app with a code emailed to them, with no passwords to forget.";
|
|
1983
|
+
};
|
|
1984
|
+
};
|
|
1937
1985
|
readonly strings: {
|
|
1938
1986
|
readonly $schema: "./schema.json";
|
|
1939
1987
|
readonly format: 1;
|
package/dist/capabilities.gen.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by `npm run capabilities:build` from capabilities/*.json. Do not edit.
|
|
2
2
|
// check:capabilities fails in Checks when this file is stale.
|
|
3
|
-
export const MANIFEST_VERSION = "
|
|
3
|
+
export const MANIFEST_VERSION = "984099bc7d3f";
|
|
4
4
|
export const capabilities = {
|
|
5
5
|
"ai": {
|
|
6
6
|
"$schema": "./schema.json",
|
|
@@ -637,9 +637,7 @@ export const capabilities = {
|
|
|
637
637
|
"sb_query",
|
|
638
638
|
"sb_project_delete",
|
|
639
639
|
"cf_worker_secret_put",
|
|
640
|
-
"vendor_settings_apply"
|
|
641
|
-
"cf_turnstile_widget_create",
|
|
642
|
-
"cf_turnstile_widget_delete"
|
|
640
|
+
"vendor_settings_apply"
|
|
643
641
|
],
|
|
644
642
|
"package": [
|
|
645
643
|
"supabaseConfig",
|
|
@@ -647,10 +645,7 @@ export const capabilities = {
|
|
|
647
645
|
"createServerClient",
|
|
648
646
|
"createUserClient",
|
|
649
647
|
"createServiceClient",
|
|
650
|
-
"WorkbenchSupabaseError"
|
|
651
|
-
"createSessions",
|
|
652
|
-
"safeReturnPath",
|
|
653
|
-
"sameOrigin"
|
|
648
|
+
"WorkbenchSupabaseError"
|
|
654
649
|
]
|
|
655
650
|
},
|
|
656
651
|
"config": [
|
|
@@ -685,39 +680,6 @@ export const capabilities = {
|
|
|
685
680
|
"production"
|
|
686
681
|
],
|
|
687
682
|
"description": "The sb_secret_ key, only for code that must bypass RLS (createServiceClient); written by cf_worker_secret_put source=supabase_secret, always two-step"
|
|
688
|
-
},
|
|
689
|
-
{
|
|
690
|
-
"name": "SESSION_SECRET",
|
|
691
|
-
"required": false,
|
|
692
|
-
"secret": true,
|
|
693
|
-
"environments": [
|
|
694
|
-
"development",
|
|
695
|
-
"staging",
|
|
696
|
-
"production"
|
|
697
|
-
],
|
|
698
|
-
"description": "Seals the sign-in session cookie (createSessions). 32 random bytes written by cf_worker_secret_put source=generate:32; replacing it signs everybody out, so it is two-step. Only needed by an app with sign-in"
|
|
699
|
-
},
|
|
700
|
-
{
|
|
701
|
-
"name": "TURNSTILE_SITE_KEY",
|
|
702
|
-
"required": false,
|
|
703
|
-
"secret": false,
|
|
704
|
-
"environments": [
|
|
705
|
-
"development",
|
|
706
|
-
"staging",
|
|
707
|
-
"production"
|
|
708
|
-
],
|
|
709
|
-
"description": "The public site key of the hostname's Turnstile widget, for the sign-in bot check; written by cf_worker_secret_put source=turnstile_sitekey after cf_turnstile_widget_create. Outside development, sign-in won't send codes without it"
|
|
710
|
-
},
|
|
711
|
-
{
|
|
712
|
-
"name": "TURNSTILE_SECRET_KEY",
|
|
713
|
-
"required": false,
|
|
714
|
-
"secret": true,
|
|
715
|
-
"environments": [
|
|
716
|
-
"development",
|
|
717
|
-
"staging",
|
|
718
|
-
"production"
|
|
719
|
-
],
|
|
720
|
-
"description": "The secret of the hostname's Turnstile widget, sent only to Cloudflare's Siteverify; written by cf_worker_secret_put source=turnstile_secret. Outside development, sign-in won't send codes without it"
|
|
721
683
|
}
|
|
722
684
|
],
|
|
723
685
|
"actions": [
|
|
@@ -760,16 +722,6 @@ export const capabilities = {
|
|
|
760
722
|
"gate": "two_step_when",
|
|
761
723
|
"when": "production, after the first successful apply on that database; staging and check_only are free",
|
|
762
724
|
"ships": "5"
|
|
763
|
-
},
|
|
764
|
-
{
|
|
765
|
-
"tool": "cf_turnstile_widget_create",
|
|
766
|
-
"gate": "free",
|
|
767
|
-
"ships": "5"
|
|
768
|
-
},
|
|
769
|
-
{
|
|
770
|
-
"tool": "cf_turnstile_widget_delete",
|
|
771
|
-
"gate": "two_step",
|
|
772
|
-
"ships": "5"
|
|
773
725
|
}
|
|
774
726
|
],
|
|
775
727
|
"rules": [
|
|
@@ -814,27 +766,6 @@ export const capabilities = {
|
|
|
814
766
|
"text": "Keep Supabase's sign-in settings at the Workbench baseline (workbench-config VENDOR_SETTINGS), or record a project's change in vendor-settings.json with a reason. Control applies it with vendor_settings_apply and checks it every day.",
|
|
815
767
|
"enforced_by": "control",
|
|
816
768
|
"probe": "database.vendor-settings"
|
|
817
|
-
},
|
|
818
|
-
{
|
|
819
|
-
"id": "database.sign-in-server-side",
|
|
820
|
-
"kind": "must",
|
|
821
|
-
"text": "Sign people in through the app's own endpoints with createSessions: the browser never talks to Supabase Auth and never holds the access token, and codes are typed, never links.",
|
|
822
|
-
"enforced_by": "review",
|
|
823
|
-
"probe": "database.sign-in"
|
|
824
|
-
},
|
|
825
|
-
{
|
|
826
|
-
"id": "database.session-writes-same-origin",
|
|
827
|
-
"kind": "must",
|
|
828
|
-
"text": "Refuse a write (anything but GET or HEAD) made with the session cookie unless sameOrigin(request) is true.",
|
|
829
|
-
"enforced_by": "review",
|
|
830
|
-
"probe": "database.session-cookie"
|
|
831
|
-
},
|
|
832
|
-
{
|
|
833
|
-
"id": "database.sign-in-checks-for-bots",
|
|
834
|
-
"kind": "must",
|
|
835
|
-
"text": "Outside development, verify a Turnstile token with Siteverify (success, this hostname and the sign-in action) before asking Supabase to send or re-send a sign-in code, and send no code when either Turnstile key is missing.",
|
|
836
|
-
"enforced_by": "review",
|
|
837
|
-
"probe": "database.sign-in-bot-check"
|
|
838
769
|
}
|
|
839
770
|
],
|
|
840
771
|
"examples": [
|
|
@@ -2607,6 +2538,143 @@ export const capabilities = {
|
|
|
2607
2538
|
"when": "The client wants a PDF: an invoice, receipt, quote, certificate, report or anything else to download or print."
|
|
2608
2539
|
}
|
|
2609
2540
|
},
|
|
2541
|
+
"sign-in": {
|
|
2542
|
+
"$schema": "./schema.json",
|
|
2543
|
+
"format": 1,
|
|
2544
|
+
"component": "sign-in",
|
|
2545
|
+
"title": "Sign-in",
|
|
2546
|
+
"summary": "People sign in to the app with a code emailed to them and typed on the next screen: no password, no link and no reset email. The Worker holds the session in one sealed cookie and talks to Supabase Auth itself, so the browser never sees a token, and a Cloudflare Turnstile check runs before any code is sent. The template has it all; a page that needs a signed-in person asks for one with requireUser. Signing in proves who someone is, not what they may do: anyone can make an account.",
|
|
2547
|
+
"status": "built",
|
|
2548
|
+
"surfaces": {
|
|
2549
|
+
"tools": [
|
|
2550
|
+
"cf_worker_secret_put",
|
|
2551
|
+
"cf_turnstile_widget_create",
|
|
2552
|
+
"cf_turnstile_widget_delete"
|
|
2553
|
+
],
|
|
2554
|
+
"package": [
|
|
2555
|
+
"createSessions",
|
|
2556
|
+
"safeReturnPath",
|
|
2557
|
+
"sameOrigin",
|
|
2558
|
+
"plainAuthError"
|
|
2559
|
+
]
|
|
2560
|
+
},
|
|
2561
|
+
"config": [
|
|
2562
|
+
{
|
|
2563
|
+
"name": "SESSION_SECRET",
|
|
2564
|
+
"required": false,
|
|
2565
|
+
"secret": true,
|
|
2566
|
+
"environments": [
|
|
2567
|
+
"development",
|
|
2568
|
+
"staging",
|
|
2569
|
+
"production"
|
|
2570
|
+
],
|
|
2571
|
+
"description": "Seals the sign-in session cookie (createSessions). 32 random bytes written by cf_worker_secret_put source=generate:32; replacing it signs everybody out, so it is two-step. Only needed by an app with sign-in"
|
|
2572
|
+
},
|
|
2573
|
+
{
|
|
2574
|
+
"name": "TURNSTILE_SITE_KEY",
|
|
2575
|
+
"required": false,
|
|
2576
|
+
"secret": false,
|
|
2577
|
+
"environments": [
|
|
2578
|
+
"development",
|
|
2579
|
+
"staging",
|
|
2580
|
+
"production"
|
|
2581
|
+
],
|
|
2582
|
+
"description": "The public site key of the hostname's Turnstile widget, for the sign-in bot check; written by cf_worker_secret_put source=turnstile_sitekey after cf_turnstile_widget_create. Outside development, sign-in won't send codes without it"
|
|
2583
|
+
},
|
|
2584
|
+
{
|
|
2585
|
+
"name": "TURNSTILE_SECRET_KEY",
|
|
2586
|
+
"required": false,
|
|
2587
|
+
"secret": true,
|
|
2588
|
+
"environments": [
|
|
2589
|
+
"development",
|
|
2590
|
+
"staging",
|
|
2591
|
+
"production"
|
|
2592
|
+
],
|
|
2593
|
+
"description": "The secret of the hostname's Turnstile widget, sent only to Cloudflare's Siteverify; written by cf_worker_secret_put source=turnstile_secret. Outside development, sign-in won't send codes without it"
|
|
2594
|
+
}
|
|
2595
|
+
],
|
|
2596
|
+
"actions": [
|
|
2597
|
+
{
|
|
2598
|
+
"tool": "cf_turnstile_widget_create",
|
|
2599
|
+
"gate": "free",
|
|
2600
|
+
"ships": "5"
|
|
2601
|
+
},
|
|
2602
|
+
{
|
|
2603
|
+
"tool": "cf_turnstile_widget_delete",
|
|
2604
|
+
"gate": "two_step",
|
|
2605
|
+
"ships": "5"
|
|
2606
|
+
}
|
|
2607
|
+
],
|
|
2608
|
+
"rules": [
|
|
2609
|
+
{
|
|
2610
|
+
"id": "sign-in.server-side",
|
|
2611
|
+
"kind": "must",
|
|
2612
|
+
"text": "Sign people in through the app's own endpoints with createSessions: the browser never talks to Supabase Auth and never holds the access token, and codes are typed, never links.",
|
|
2613
|
+
"enforced_by": "review",
|
|
2614
|
+
"probe": "database.sign-in"
|
|
2615
|
+
},
|
|
2616
|
+
{
|
|
2617
|
+
"id": "sign-in.writes-same-origin",
|
|
2618
|
+
"kind": "must",
|
|
2619
|
+
"text": "Refuse a write (anything but GET or HEAD) made with the session cookie unless sameOrigin(request) is true.",
|
|
2620
|
+
"enforced_by": "review",
|
|
2621
|
+
"probe": "database.session-cookie"
|
|
2622
|
+
},
|
|
2623
|
+
{
|
|
2624
|
+
"id": "sign-in.checks-for-bots",
|
|
2625
|
+
"kind": "must",
|
|
2626
|
+
"text": "Outside development, verify a Turnstile token with Siteverify (success, this hostname and the sign-in action) before asking Supabase to send or re-send a sign-in code, and send no code when either Turnstile key is missing.",
|
|
2627
|
+
"enforced_by": "review",
|
|
2628
|
+
"probe": "database.sign-in-bot-check"
|
|
2629
|
+
}
|
|
2630
|
+
],
|
|
2631
|
+
"probes": [],
|
|
2632
|
+
"proved_by": [
|
|
2633
|
+
"database"
|
|
2634
|
+
],
|
|
2635
|
+
"offer": {
|
|
2636
|
+
"pattern": "switch_on",
|
|
2637
|
+
"when": "The client wants people to sign in or have accounts: a members' area, saved orders or bookings, a profile, \"my account\", or a page only some people may see.",
|
|
2638
|
+
"say": "Sign-in is already built in: people get a code by email and type it, so there are no passwords to forget or leak. I'll put the pages that need it behind it. Here's what I think needs sign-in; tell me what to change.",
|
|
2639
|
+
"answers_file": "workbench/sign-in.json",
|
|
2640
|
+
"answers_format": "`{ \"format\": 1, \"status\": \"draft\" | \"done\", \"pages\": [{ \"path\": \"/orders\", \"what\": \"<what it shows or does, in the client's words>\", \"who\": \"signed_in\" | \"owner\" | \"admins\", \"fresh\": true | false }] }`, one entry per page or action that needs a signed-in person. `who` is `signed_in` (anyone with an account), `owner` (only the person the thing belongs to, enforced by row-level security on their user id through `dbAs`) or `admins` (only people in `app_admins`). `fresh` is true where it also needs a code typed in the last 15 minutes. Save it as a draft until the client accepts the table. Then build each page with `requireUser`, enforce its `who`, and check `fresh` on the ones marked so.",
|
|
2641
|
+
"questions": [
|
|
2642
|
+
{
|
|
2643
|
+
"id": "sign-in.pages",
|
|
2644
|
+
"sets": [
|
|
2645
|
+
"page.sign_in",
|
|
2646
|
+
"page.who",
|
|
2647
|
+
"page.fresh"
|
|
2648
|
+
],
|
|
2649
|
+
"ask": "Here are the pages and actions that need someone signed in, who among them may use each one, and which also need a fresh code because they show or change something sensitive. Accept them, or change some?",
|
|
2650
|
+
"style": "table",
|
|
2651
|
+
"note": "Propose from the pages built or planned. Anything that shows or changes a person's own things (orders, bookings, a profile) needs sign-in, with `who` set to `owner`. Signing in only proves who someone is, and anyone can make an account, so a page for staff or admins must say `admins`: `requireUser` alone would let any stranger who signs up use it. Mark `fresh` where a stranger holding a signed-in phone could do harm: showing payment details, changing an email address, adding an admin, deleting an account. The template has no re-check screen: when `fresh` is false, the page tells the person to sign out and sign in again with a new code, as the admins section of `/admin/brand` does. Pages anyone may read, such as the home page, a menu or a contact form, stay open. Only the client knows what is private and who may do what, so always show the table."
|
|
2652
|
+
}
|
|
2653
|
+
],
|
|
2654
|
+
"not_asked": [
|
|
2655
|
+
{
|
|
2656
|
+
"sets": "sign_in.method",
|
|
2657
|
+
"why": "The template signs people in with an emailed code only (Workbench's choice: no password to forget, leak or reuse), and the authentication standard rules out sign-in links. Passwords and social sign-in (\"sign in with Google\") aren't built. If the client asks for one, say so and that it is the Workbench owner's call; don't build it."
|
|
2658
|
+
},
|
|
2659
|
+
{
|
|
2660
|
+
"sets": "sign_in.length",
|
|
2661
|
+
"why": "Sessions last weeks, with a cap, as the authentication standard (§10) asks. The package's defaults (SESSION_DEFAULTS) sign a person out after 14 days without a visit and 30 days after the code was typed, whatever happens; on a shared device they untick \"keep me signed in\" and are signed out after 30 minutes idle. Say so if the client asks, and don't change them."
|
|
2662
|
+
},
|
|
2663
|
+
{
|
|
2664
|
+
"sets": "sign_in.who",
|
|
2665
|
+
"why": "Anyone with an email address can make an account; invite-only sign-up isn't built yet. If the client needs only invited people, say it isn't built yet and ask them how they want to handle it. Don't build an invite system of your own."
|
|
2666
|
+
},
|
|
2667
|
+
{
|
|
2668
|
+
"sets": "sign_in.bot_check",
|
|
2669
|
+
"why": "The Turnstile check is always on outside development. Control sets it up (cf_turnstile_widget_create, then cf_worker_secret_put sources turnstile_sitekey and turnstile_secret); there is nothing to ask."
|
|
2670
|
+
}
|
|
2671
|
+
]
|
|
2672
|
+
},
|
|
2673
|
+
"delivery": {
|
|
2674
|
+
"choice": "built_in",
|
|
2675
|
+
"intro": "People signing in to the app with a code emailed to them, with no passwords to forget."
|
|
2676
|
+
}
|
|
2677
|
+
},
|
|
2610
2678
|
"strings": {
|
|
2611
2679
|
"$schema": "./schema.json",
|
|
2612
2680
|
"format": 1,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export declare const COMPONENTS: readonly ["gateway", "ai", "images", "image-search", "config", "email", "email-inbound", "email-sending", "database", "workers", "domains", "dns", "analytics", "country", "pdf", "access", "r2", "kv", "github", "strings", "ui", "forms"];
|
|
2
|
+
export declare const COMPONENTS: readonly ["gateway", "ai", "images", "image-search", "config", "email", "email-inbound", "email-sending", "database", "workers", "domains", "dns", "analytics", "country", "pdf", "access", "r2", "kv", "github", "strings", "ui", "forms", "sign-in"];
|
|
3
3
|
export type Component = (typeof COMPONENTS)[number];
|
|
4
4
|
export declare const allowedSchemas: {
|
|
5
5
|
gateway: z.ZodObject<{
|
|
@@ -179,6 +179,7 @@ export declare const capabilitySchema: z.ZodObject<{
|
|
|
179
179
|
kv: "kv";
|
|
180
180
|
pdf: "pdf";
|
|
181
181
|
r2: "r2";
|
|
182
|
+
"sign-in": "sign-in";
|
|
182
183
|
strings: "strings";
|
|
183
184
|
ui: "ui";
|
|
184
185
|
workers: "workers";
|
|
@@ -289,6 +290,31 @@ export declare const capabilitySchema: z.ZodObject<{
|
|
|
289
290
|
two_step: z.ZodOptional<z.ZodBoolean>;
|
|
290
291
|
note: z.ZodOptional<z.ZodString>;
|
|
291
292
|
}, z.core.$strict>>;
|
|
293
|
+
proved_by: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
294
|
+
access: "access";
|
|
295
|
+
ai: "ai";
|
|
296
|
+
analytics: "analytics";
|
|
297
|
+
config: "config";
|
|
298
|
+
country: "country";
|
|
299
|
+
database: "database";
|
|
300
|
+
dns: "dns";
|
|
301
|
+
domains: "domains";
|
|
302
|
+
email: "email";
|
|
303
|
+
"email-inbound": "email-inbound";
|
|
304
|
+
"email-sending": "email-sending";
|
|
305
|
+
forms: "forms";
|
|
306
|
+
gateway: "gateway";
|
|
307
|
+
github: "github";
|
|
308
|
+
"image-search": "image-search";
|
|
309
|
+
images: "images";
|
|
310
|
+
kv: "kv";
|
|
311
|
+
pdf: "pdf";
|
|
312
|
+
r2: "r2";
|
|
313
|
+
"sign-in": "sign-in";
|
|
314
|
+
strings: "strings";
|
|
315
|
+
ui: "ui";
|
|
316
|
+
workers: "workers";
|
|
317
|
+
}>>>;
|
|
292
318
|
offer: z.ZodOptional<z.ZodObject<{
|
|
293
319
|
pattern: z.ZodEnum<{
|
|
294
320
|
drafts: "drafts";
|
|
@@ -25,6 +25,7 @@ export const COMPONENTS = [
|
|
|
25
25
|
"strings",
|
|
26
26
|
"ui",
|
|
27
27
|
"forms",
|
|
28
|
+
"sign-in",
|
|
28
29
|
];
|
|
29
30
|
const id = z.string().regex(/^[a-z0-9][a-z0-9.-]*$/, "lower case, digits, dots and hyphens");
|
|
30
31
|
const code = z.string().regex(/^[a-z][a-z0-9_]*$/, "a snake_case error code");
|
|
@@ -226,7 +227,14 @@ const baseShape = {
|
|
|
226
227
|
errors: z.array(errorEntry).min(1).optional(),
|
|
227
228
|
rules: z.array(rule).min(1),
|
|
228
229
|
examples: z.array(example).min(1).optional(),
|
|
229
|
-
|
|
230
|
+
/** Empty only with `proved_by`, when every rule's probe lives in those manifests. */
|
|
231
|
+
probes: z.array(probe),
|
|
232
|
+
/**
|
|
233
|
+
* Components whose probes may prove this one's rules, when one run proves
|
|
234
|
+
* both (sign-in's rules are proved by database's sign-in probes, whose ids
|
|
235
|
+
* wb-probe's run history keys on).
|
|
236
|
+
*/
|
|
237
|
+
proved_by: z.array(z.enum(COMPONENTS)).min(1).optional(),
|
|
230
238
|
offer: offer.optional(),
|
|
231
239
|
/** Why a client has nothing to answer for this component (plumbing). A manifest has `offer` or `no_offer`. */
|
|
232
240
|
no_offer: z.string().min(1).optional(),
|
|
@@ -307,6 +315,8 @@ export function checkManifests(files) {
|
|
|
307
315
|
const problems = [];
|
|
308
316
|
const manifests = [];
|
|
309
317
|
const globalIds = new Map();
|
|
318
|
+
// A rule may name a probe in a `proved_by` manifest; checked once every file is read.
|
|
319
|
+
const deferred = [];
|
|
310
320
|
for (const [file, json] of Object.entries(files)) {
|
|
311
321
|
const parsed = capabilitySchema.safeParse(json);
|
|
312
322
|
if (!parsed.success) {
|
|
@@ -344,10 +354,9 @@ export function checkManifests(files) {
|
|
|
344
354
|
seen("limit", l.id);
|
|
345
355
|
for (const e of m.examples ?? [])
|
|
346
356
|
seen("example", e.id);
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
357
|
+
deferred.push({ file, m, own: probeIds });
|
|
358
|
+
if (m.probes.length === 0 && !m.proved_by)
|
|
359
|
+
problems.push({ file, check: "proof", message: "has no probes: add one, or name in proved_by the component whose probes prove these rules" });
|
|
351
360
|
for (const e of m.examples ?? []) {
|
|
352
361
|
if (!probeIds.has(e.probe))
|
|
353
362
|
problems.push({ file, check: "proof", message: `example ${e.id} names probe ${e.probe}, which isn't in this file` });
|
|
@@ -388,5 +397,21 @@ export function checkManifests(files) {
|
|
|
388
397
|
if (m.delivery && !m.offer && !m.delivery.when)
|
|
389
398
|
problems.push({ file, check: "schema", message: "delivery needs when, because there is no offer to say when the tool applies" });
|
|
390
399
|
}
|
|
400
|
+
const byComponent = new Map(manifests.map((m) => [m.component, m]));
|
|
401
|
+
for (const { file, m, own } of deferred) {
|
|
402
|
+
const provers = (m.proved_by ?? []).flatMap((c) => {
|
|
403
|
+
const prover = byComponent.get(c);
|
|
404
|
+
if (!prover)
|
|
405
|
+
problems.push({ file, check: "proof", message: `proved_by names ${c}, which isn't among the manifests` });
|
|
406
|
+
else if (m.status === "built" && prover.status !== "built")
|
|
407
|
+
problems.push({ file, check: "proof", message: `proved_by names ${c}, which isn't built` });
|
|
408
|
+
return prover ? [prover] : [];
|
|
409
|
+
});
|
|
410
|
+
for (const r of m.rules) {
|
|
411
|
+
if (own.has(r.probe) || provers.some((p) => p.probes.some((x) => x.id === r.probe)))
|
|
412
|
+
continue;
|
|
413
|
+
problems.push({ file, check: "proof", message: `rule ${r.id} names probe ${r.probe}, which isn't in this file${m.proved_by ? " or a proved_by manifest" : ""}` });
|
|
414
|
+
}
|
|
415
|
+
}
|
|
391
416
|
return { manifests, problems };
|
|
392
417
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mccaskie-labs/workbench-config",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Workbench app configuration: WORKBENCH_ENV, typed variables that fail by name, secrets that are never printed, and the capabilities manifest.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|