@seliseblocks/cli-os 0.2.10 → 0.2.11

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.
@@ -1,124 +1,124 @@
1
- # Object management
2
-
3
- Use `blocks data files *` from a terminal and `blocksClient.data.directories` / `blocksClient.data.objects` / `blocksClient.data.files` in app code. Do not fall back to legacy `data.dms.*` helpers or raw HTTP.
4
-
5
- ## Browse a directory
6
-
7
- Use the current `get-objects` operation with:
8
-
9
- - `parentDirectoryId` for a concrete directory, or `moduleName` to resolve a module's default directory when no parent id is supplied.
10
- - `cursor` from the previous response and `limit` from 1–200 (default 50).
11
- - `type: "directory" | "file"` to narrow results.
12
- - `search` for name filtering within that parent.
13
-
14
- The result contains `items`, `nextCursor`, `hasMore`, and `totalChildCount`. Each item has a lowercase `type` discriminator and permission flags. Continue while `hasMore` using `nextCursor`; access filtering can produce a short page even when more results remain.
15
-
16
- Use `search-objects` for a case-insensitive name search across descendants. Pass `query`, optional `directoryId`, optional type, cursor, and limit. Search text is treated literally rather than as a regular expression.
17
-
18
- ```bash
19
- blocks data files list --parent-id <directoryId> --type file --limit 50 --json
20
- blocks data files search invoice --directory-id <directoryId> --json
21
- ```
22
-
23
- ```ts
24
- const page = await blocksClient.data.objects.list({ parentDirectoryId, limit: 50 });
25
- const matches = await blocksClient.data.objects.search({ query: "invoice", directoryId });
26
- ```
27
-
28
- ## Manage directories
29
-
30
- - **Create:** provide `name`, an existing `parentDirectoryId`, optional `description`, `configurationName`, and `allowedFileExtensions`. If the parent is empty but `moduleName` is present, the backend resolves the module default. Creating a true root uses a separate owner-only capability and should not be a normal app action.
31
- - **Read details:** get one directory by `directoryId`. The response includes path, ancestors, child counts, size, extension rules, inheritance, timestamps, and permissions.
32
- - **Update:** provide `directoryId` and optional `name`/`description`. Default directories cannot be renamed.
33
- - **Move:** provide `directoryId` and `targetDirectoryId`; an empty target means top level. Moving into self/descendants is rejected. Default directories cannot move.
34
- - **Delete:** send `permanent: false` for trash. Permanent deletion requires an empty directory. Default directories cannot be deleted.
35
-
36
- Directory names must be 1–255 characters, trimmed, not `.`/`..`, and contain no slash or backslash. Descriptions are limited to 2,000 characters.
37
-
38
- ```bash
39
- blocks data files directory-create Contracts --parent-id <directoryId> \
40
- --allowed-extensions pdf,docx --dry-run --json
41
- blocks data files directory-get <directoryId> --json
42
- blocks data files directory-update <directoryId> --name Agreements --dry-run --json
43
- blocks data files directory-move <directoryId> --target-directory-id <targetId> --dry-run --json
44
- blocks data files directory-delete <directoryId> --dry-run --json
45
- ```
46
-
47
- Use the matching `blocksClient.data.directories.create/get/update/move/delete` methods in app code.
48
-
49
- ## Manage files
50
-
51
- - **Rename:** needs Edit and a unique name in the current directory.
52
- - **Move:** needs Delete on the source file and Edit on the target directory. Stored bytes and versions stay in place.
53
- - **Copy:** needs View on the source and Edit on the target. The new file gets a new id; version rows reference the same immutable stored bytes. It inherits from the target. Set `copyAccessPolicies` only when the user intends to duplicate direct policy entries.
54
- - **Versions:** list newest-first with a cursor and limit 1–100. Creating a cloud version returns `versionNo` and `uploadUrl`; PUT the bytes to that URL without Blocks auth headers.
55
-
56
- Move/copy reject a target name collision and a file extension disallowed by the target directory.
57
-
58
- ```bash
59
- blocks data files rename <fileId> --name final.pdf --dry-run --json
60
- blocks data files move <fileId> --target-directory-id <targetId> --dry-run --json
61
- blocks data files copy <fileId> --target-directory-id <targetId> --dry-run --json
62
- ```
63
-
64
- Use `blocksClient.data.files.rename`, `.move`, and `.copy` in app code.
65
-
66
- ## Trash and restore
67
-
68
- Use soft delete (`permanent: false`) to archive an item. List archived files/directories with `get-trash`, optionally filtering by type and paging with cursor/limit. Restore with `restore-from-trash`; permanently purge an archived item with `delete-from-trash` only after explicit approval.
69
-
70
- Restore returns the item to its original parent. Reads and mutations remain ACL-filtered while the item is archived.
71
-
72
- ```bash
73
- blocks data files trash --type file --json
74
- blocks data files restore <resourceId> --dry-run --json
75
- blocks data files purge <resourceId> --dry-run --json
76
- ```
77
-
78
- Use `blocksClient.data.objects.trash`, `.restore`, and `.deleteFromTrash` in app code.
79
-
80
- ## Shared objects
81
-
82
- Use `get-shared-objects` for live items shared with the caller. It is cursor-paginated and may be filtered by `directory`/`file`. Owned objects are excluded. Direct or inherited allow entries for the current user, role, or organization qualify as shares.
83
-
84
- Use `share-object` for the common allow-only action. Provide:
85
-
86
- - `resourceId`
87
- - `resourceType`: `Directory` or `File`
88
- - `principalType`: `User`, `Role`, `Organization`, or `Everyone`
89
- - `principalId` for every type except `Everyone`
90
- - `permission`: `View`, `Download`, `Edit`, `Delete`, `Manage`, or `Owner`
91
- - optional future `expiresAt`
92
-
93
- Sharing requires Manage on the resource.
94
-
95
- ```bash
96
- blocks data files shared --json
97
- blocks data files share <resourceId> --resource-type Directory \
98
- --principal-type Role --principal-id editors --permission Edit --dry-run --json
99
- ```
100
-
101
- Use `blocksClient.data.objects.shared` and `.share` in app code.
102
-
103
- ## Advanced access policies
104
-
105
- Use the policy operations only for an access-management UI:
106
-
107
- - `get-access-policies` lists direct entries on a resource.
108
- - `grant-access` adds Allow or Deny with optional priority and expiry.
109
- - `update-access-policy` replaces an entry and requires its `policyItemId`.
110
- - `revoke-access-policy` removes an entry by `resourceId` and `policyItemId`.
111
- - `resolve-access` returns the caller's `canView`, `canDownload`, `canEdit`, `canDelete`, `canManage`, and `canOwner` flags.
112
- - `toggle-inheritance` changes whether ancestors participate in resolution.
113
-
114
- Enums are JSON strings, not guessed numeric values. Priority must be non-negative and expiry must be in the future.
115
-
116
- Policy guardrails:
117
-
118
- - Manage is required to change access.
119
- - A Deny aimed at the resource owner is rejected.
120
- - Turning inheritance off is rejected until the resource has a direct Allow entry, preventing an orphaned resource.
121
- - The request model for access-policy listing contains `includeInherited`, but the current controller does not use it; do not promise inherited entries in that response. Use `resolve-access` for effective permissions.
122
- - Object reads may return 404 instead of 403 to avoid revealing hidden ids.
123
-
124
- CLI access commands are `access-list`, `access-grant`, `access-update`, `access-revoke`, `access-resolve`, and `inheritance`. Their SDK equivalents are under `blocksClient.data.objects`.
1
+ # Object management
2
+
3
+ Use `blocks data files *` from a terminal and `blocksClient.data.directories` / `blocksClient.data.objects` / `blocksClient.data.files` in app code. Do not fall back to legacy `data.dms.*` helpers or raw HTTP.
4
+
5
+ ## Browse a directory
6
+
7
+ Use the current `get-objects` operation with:
8
+
9
+ - `parentDirectoryId` for a concrete directory, or `moduleName` to resolve a module's default directory when no parent id is supplied.
10
+ - `cursor` from the previous response and `limit` from 1–200 (default 50).
11
+ - `type: "directory" | "file"` to narrow results.
12
+ - `search` for name filtering within that parent.
13
+
14
+ The result contains `items`, `nextCursor`, `hasMore`, and `totalChildCount`. Each item has a lowercase `type` discriminator and permission flags. Continue while `hasMore` using `nextCursor`; access filtering can produce a short page even when more results remain.
15
+
16
+ Use `search-objects` for a case-insensitive name search across descendants. Pass `query`, optional `directoryId`, optional type, cursor, and limit. Search text is treated literally rather than as a regular expression.
17
+
18
+ ```bash
19
+ blocks data files list --parent-id <directoryId> --type file --limit 50 --json
20
+ blocks data files search invoice --directory-id <directoryId> --json
21
+ ```
22
+
23
+ ```ts
24
+ const page = await blocksClient.data.objects.list({ parentDirectoryId, limit: 50 });
25
+ const matches = await blocksClient.data.objects.search({ query: "invoice", directoryId });
26
+ ```
27
+
28
+ ## Manage directories
29
+
30
+ - **Create:** provide `name`, an existing `parentDirectoryId`, optional `description`, `configurationName`, and `allowedFileExtensions`. If the parent is empty but `moduleName` is present, the backend resolves the module default. Creating a true root uses a separate owner-only capability and should not be a normal app action.
31
+ - **Read details:** get one directory by `directoryId`. The response includes path, ancestors, child counts, size, extension rules, inheritance, timestamps, and permissions.
32
+ - **Update:** provide `directoryId` and optional `name`/`description`. Default directories cannot be renamed.
33
+ - **Move:** provide `directoryId` and `targetDirectoryId`; an empty target means top level. Moving into self/descendants is rejected. Default directories cannot move.
34
+ - **Delete:** send `permanent: false` for trash. Permanent deletion requires an empty directory. Default directories cannot be deleted.
35
+
36
+ Directory names must be 1–255 characters, trimmed, not `.`/`..`, and contain no slash or backslash. Descriptions are limited to 2,000 characters.
37
+
38
+ ```bash
39
+ blocks data files directory-create Contracts --parent-id <directoryId> \
40
+ --allowed-extensions pdf,docx --dry-run --json
41
+ blocks data files directory-get <directoryId> --json
42
+ blocks data files directory-update <directoryId> --name Agreements --dry-run --json
43
+ blocks data files directory-move <directoryId> --target-directory-id <targetId> --dry-run --json
44
+ blocks data files directory-delete <directoryId> --dry-run --json
45
+ ```
46
+
47
+ Use the matching `blocksClient.data.directories.create/get/update/move/delete` methods in app code.
48
+
49
+ ## Manage files
50
+
51
+ - **Rename:** needs Edit and a unique name in the current directory.
52
+ - **Move:** needs Delete on the source file and Edit on the target directory. Stored bytes and versions stay in place.
53
+ - **Copy:** needs View on the source and Edit on the target. The new file gets a new id; version rows reference the same immutable stored bytes. It inherits from the target. Set `copyAccessPolicies` only when the user intends to duplicate direct policy entries.
54
+ - **Versions:** list newest-first with a cursor and limit 1–100. Creating a cloud version returns `versionNo` and `uploadUrl`; PUT the bytes to that URL without Blocks auth headers.
55
+
56
+ Move/copy reject a target name collision and a file extension disallowed by the target directory.
57
+
58
+ ```bash
59
+ blocks data files rename <fileId> --name final.pdf --dry-run --json
60
+ blocks data files move <fileId> --target-directory-id <targetId> --dry-run --json
61
+ blocks data files copy <fileId> --target-directory-id <targetId> --dry-run --json
62
+ ```
63
+
64
+ Use `blocksClient.data.files.rename`, `.move`, and `.copy` in app code.
65
+
66
+ ## Trash and restore
67
+
68
+ Use soft delete (`permanent: false`) to archive an item. List archived files/directories with `get-trash`, optionally filtering by type and paging with cursor/limit. Restore with `restore-from-trash`; permanently purge an archived item with `delete-from-trash` only after explicit approval.
69
+
70
+ Restore returns the item to its original parent. Reads and mutations remain ACL-filtered while the item is archived.
71
+
72
+ ```bash
73
+ blocks data files trash --type file --json
74
+ blocks data files restore <resourceId> --dry-run --json
75
+ blocks data files purge <resourceId> --dry-run --json
76
+ ```
77
+
78
+ Use `blocksClient.data.objects.trash`, `.restore`, and `.deleteFromTrash` in app code.
79
+
80
+ ## Shared objects
81
+
82
+ Use `get-shared-objects` for live items shared with the caller. It is cursor-paginated and may be filtered by `directory`/`file`. Owned objects are excluded. Direct or inherited allow entries for the current user, role, or organization qualify as shares.
83
+
84
+ Use `share-object` for the common allow-only action. Provide:
85
+
86
+ - `resourceId`
87
+ - `resourceType`: `Directory` or `File`
88
+ - `principalType`: `User`, `Role`, `Organization`, or `Everyone`
89
+ - `principalId` for every type except `Everyone`
90
+ - `permission`: `View`, `Download`, `Edit`, `Delete`, `Manage`, or `Owner`
91
+ - optional future `expiresAt`
92
+
93
+ Sharing requires Manage on the resource.
94
+
95
+ ```bash
96
+ blocks data files shared --json
97
+ blocks data files share <resourceId> --resource-type Directory \
98
+ --principal-type Role --principal-id editors --permission Edit --dry-run --json
99
+ ```
100
+
101
+ Use `blocksClient.data.objects.shared` and `.share` in app code.
102
+
103
+ ## Advanced access policies
104
+
105
+ Use the policy operations only for an access-management UI:
106
+
107
+ - `get-access-policies` lists direct entries on a resource.
108
+ - `grant-access` adds Allow or Deny with optional priority and expiry.
109
+ - `update-access-policy` replaces an entry and requires its `policyItemId`.
110
+ - `revoke-access-policy` removes an entry by `resourceId` and `policyItemId`.
111
+ - `resolve-access` returns the caller's `canView`, `canDownload`, `canEdit`, `canDelete`, `canManage`, and `canOwner` flags.
112
+ - `toggle-inheritance` changes whether ancestors participate in resolution.
113
+
114
+ Enums are JSON strings, not guessed numeric values. Priority must be non-negative and expiry must be in the future.
115
+
116
+ Policy guardrails:
117
+
118
+ - Manage is required to change access.
119
+ - A Deny aimed at the resource owner is rejected.
120
+ - Turning inheritance off is rejected until the resource has a direct Allow entry, preventing an orphaned resource.
121
+ - The request model for access-policy listing contains `includeInherited`, but the current controller does not use it; do not promise inherited entries in that response. Use `resolve-access` for effective permissions.
122
+ - Object reads may return 404 instead of 403 to avoid revealing hidden ids.
123
+
124
+ CLI access commands are `access-list`, `access-grant`, `access-update`, `access-revoke`, `access-resolve`, and `inheritance`. Their SDK equivalents are under `blocksClient.data.objects`.
@@ -1,100 +1,100 @@
1
- ---
2
- name: blocks-frontend-local-https
3
- description: "Run a scaffolded (`blocks new web`) Blocks app locally over HTTPS on its real project domain — required for hosted IAM login, since plain HTTP or localhost never gets the session cookie. The scaffold already automates cert generation (npm run cert, no OpenSSL needed) and HTTPS serving via vite.config.ts. Covers running that flow, trusting the cert, the hosts-file entry, and troubleshooting. Use when running a scaffolded app over HTTPS, hitting 'SSO cookie not set' / Vite 'Blocked request' errors, trusting the dev cert, or asking why local login redirects back but doesn't stay signed in."
4
- ---
5
-
6
- # Blocks Frontend — Local HTTPS for a Scaffolded App
7
-
8
- This is the local dev loop for an app already created with `blocks new web` (see the blocks-onboarding skill for the scaffold command itself — this skill doesn't repeat it). The scaffold **already generates its own cert tooling**; nothing here is built from scratch, and nothing uses raw `openssl`/`curl`/`fetch`.
9
-
10
- ## Why localhost doesn't work
11
-
12
- Browser login goes through the hosted Blocks IAM IdP flow (`blocksClient.auth.idp.redirectToProvider()` / `.callback()`), which finishes by IAM setting a **Secure, domain-scoped session cookie**. Browsers won't store or send that cookie on plain `http://localhost` — it has to be HTTPS, and it has to be on the project's real domain, or the cookie silently never lands and the app looks "logged in" for a second then bounces back to logged-out.
13
-
14
- ## Where the domain comes from
15
-
16
- `--app-domain` on `blocks new web` is the app's real Blocks origin, e.g. `https://dbpdba.seliseblocks.com`. The generated `.env` keeps two derived values:
17
-
18
- - `VITE_BLOCKS_APP_DOMAIN` — the full value as passed, with scheme (`https://dbpdba.seliseblocks.com`).
19
- - `VITE_BLOCKS_DEV_HOST` — the same host with no scheme (`dbpdba.seliseblocks.com`), computed by the scaffold generator and used everywhere locally: hosts file, cert `commonName`/SAN, and `vite.config.ts`'s `server.host`/`allowedHosts`.
20
-
21
- You never need to look this up separately — it's already sitting in `.env` after scaffolding.
22
-
23
- ## The flow
24
-
25
- ```bash
26
- cd <appName>
27
- npm install
28
- npm run cert
29
- ```
30
-
31
- `npm run cert` runs `scripts/generate-cert.mjs`, which reads `VITE_BLOCKS_DEV_HOST` from `.env` (or `process.env`, or an explicit `npm run cert -- <domain>` argument) and uses the `selfsigned` npm dependency to write `.cert/dev-key.pem` and `.cert/dev-cert.pem`, with `subjectAltName` covering the domain, `localhost`, and `127.0.0.1`. This is pure Node — it works from a plain PowerShell prompt with no OpenSSL and no Git Bash/WSL switch.
32
-
33
- Next, map the domain to your machine — add one line to the hosts file (needs admin/elevated):
34
-
35
- ```text
36
- 127.0.0.1 dbpdba.seliseblocks.com
37
- ```
38
-
39
- Windows path: `C:\Windows\System32\drivers\etc\hosts` (edit as Administrator).
40
-
41
- Then:
42
-
43
- ```bash
44
- npm run dev
45
- ```
46
-
47
- `vite.config.ts` picks up `.cert/dev-key.pem` + `.cert/dev-cert.pem` automatically when both exist and serves HTTPS on `VITE_BLOCKS_DEV_HOST`:`VITE_BLOCKS_DEV_PORT` (default port `5173`, `strictPort: true` because the port is baked into the registered OIDC redirect URI). It also sets `allowedHosts` to that domain, working around Vite's default DNS-rebinding protection which otherwise 404s custom hosts with "Blocked request."
48
-
49
- Finally, open the app at:
50
-
51
- ```text
52
- https://dbpdba.seliseblocks.com:5173
53
- ```
54
-
55
- **Not** `http://`, **not** `localhost` — either one skips the cookie entirely even though the app loads.
56
-
57
- ## Trusting the cert (optional but recommended)
58
-
59
- The cert is self-signed, so the browser shows a one-time warning until trusted. `npm run cert` prints the exact trust command for your OS when it finishes:
60
-
61
- - Windows (elevated prompt): `certutil -addstore -f Root .cert\dev-cert.pem`
62
- - macOS: `sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain .cert/dev-cert.pem`
63
- - Linux: `sudo cp .cert/dev-cert.pem /usr/local/share/ca-certificates/blocks-dev.crt && sudo update-ca-certificates`
64
-
65
- Restart the browser after trusting so it picks up the new trust store entry. `.cert/` is already gitignored by the scaffold — it's per-machine, never committed.
66
-
67
- ## Still need a public OIDC client
68
-
69
- Local HTTPS alone doesn't make login succeed if no OIDC client is registered yet, or if its `redirect_uris` don't include this exact dev origin. That registration is portal-only (see blocks-onboarding's Gotchas) — out of scope here, but it's the next thing to check if HTTPS is right and login still fails. The redirect URI must match byte-for-byte, including `:5173`.
70
-
71
- ## Gotchas
72
-
73
- **Custom app domain: the API base URL must share its registrable domain, or the cookie never lands.** `blocks new web --app-domain` isn't limited to `*.seliseblocks.com` — it also supports custom domains (e.g. `abc.slsblx.com`, `xyz.blx10.com`). On a custom domain, the hosted-login session cookie is only stored by the browser if `VITE_BLOCKS_API_URL` shares the app's registrable domain. Concretely:
74
-
75
- - `abc.slsblx.com` → `VITE_BLOCKS_API_URL` must be `https://blocksapi.slsblx.com`
76
- - `xyz.blx10.com` → `VITE_BLOCKS_API_URL` must be `https://blocksapi.blx10.com`
77
-
78
- If `VITE_BLOCKS_API_URL` is left at the default `https://api.seliseblocks.com` while the app itself runs on a custom domain, the browser treats the API as cross-site relative to the app and never stores the cookie — login still redirects back and *looks* successful, but cookie-based calls (`/iam/me`, organization switching, logout) silently fail. Check `VITE_BLOCKS_API_URL` in `.env` first whenever the app domain is not `*.seliseblocks.com` and auth-dependent calls are failing despite HTTPS and the cert being set up correctly.
79
-
80
- ## Troubleshooting
81
-
82
- | Symptom | Likely cause | Fix |
83
- |---|---|---|
84
- | Login redirects back but app shows logged-out | Opened on `http://` or `localhost` instead of the HTTPS dev host | Check the URL bar: must be `https://<VITE_BLOCKS_DEV_HOST>:5173` exactly |
85
- | Browser "not private" warning | Self-signed cert not trusted yet | Run the OS trust command `npm run cert` printed, then restart the browser |
86
- | Vite: "Blocked request. This host is not allowed" | `allowedHosts` doesn't include the host being requested — usually `.env`'s `VITE_BLOCKS_DEV_HOST` was edited/missing after scaffold, or the browser is hitting a different hostname than expected | Confirm `.env` has the right `VITE_BLOCKS_DEV_HOST` and that the URL bar matches it exactly |
87
- | Page doesn't load at all / DNS error | Hosts file entry missing or wrong | Add `127.0.0.1 <VITE_BLOCKS_DEV_HOST>` to `C:\Windows\System32\drivers\etc\hosts` (as Administrator) |
88
- | `npm run cert` errors "No domain given" | `.env` missing or `VITE_BLOCKS_DEV_HOST` not set | Confirm `.env` exists with `VITE_BLOCKS_DEV_HOST=<host>`, or run `npm run cert -- <domain>` explicitly |
89
- | `npm run dev` fails to bind the port | Port 5173 already in use (dev server uses `strictPort`, won't fall back) | Free port 5173, or the port is fixed because it's part of the registered OIDC redirect URI — don't just change it without updating the OIDC client too |
90
- | HTTPS works, cert trusted, login still fails | No OIDC client registered yet, or its redirect URI doesn't match this origin exactly | Register/update the public OIDC client in the portal with `https://<VITE_BLOCKS_DEV_HOST>:5173/login/callback` as a redirect URI (portal-only, see blocks-onboarding) |
91
- | Works for one dev, fails for a teammate | Each machine needs its own cert + hosts entry — `.cert/` is gitignored on purpose | Teammate runs `npm run cert` and adds the hosts entry on their own machine |
92
-
93
- ## Example trigger prompts
94
-
95
- - "Run my app locally over HTTPS on its real domain so SSO works"
96
- - "My local login isn't working — it just bounces back to the login page"
97
- - "How do I set up the dev cert for this scaffolded app?"
98
- - "I'm getting 'Blocked request. This host is not allowed' from Vite"
99
- - "Do I need OpenSSL to run npm run cert on Windows?"
100
- - "Why does login work in production but not on localhost?"
1
+ ---
2
+ name: blocks-frontend-local-https
3
+ description: "Run a scaffolded (`blocks new web`) Blocks app locally over HTTPS on its real project domain — required for hosted IAM login, since plain HTTP or localhost never gets the session cookie. The scaffold already automates cert generation (npm run cert, no OpenSSL needed) and HTTPS serving via vite.config.ts. Covers running that flow, trusting the cert, the hosts-file entry, and troubleshooting. Use when running a scaffolded app over HTTPS, hitting 'SSO cookie not set' / Vite 'Blocked request' errors, trusting the dev cert, or asking why local login redirects back but doesn't stay signed in."
4
+ ---
5
+
6
+ # Blocks Frontend — Local HTTPS for a Scaffolded App
7
+
8
+ This is the local dev loop for an app already created with `blocks new web` (see the blocks-onboarding skill for the scaffold command itself — this skill doesn't repeat it). The scaffold **already generates its own cert tooling**; nothing here is built from scratch, and nothing uses raw `openssl`/`curl`/`fetch`.
9
+
10
+ ## Why localhost doesn't work
11
+
12
+ Browser login goes through the hosted Blocks IAM IdP flow (`blocksClient.auth.idp.redirectToProvider()` / `.callback()`), which finishes by IAM setting a **Secure, domain-scoped session cookie**. Browsers won't store or send that cookie on plain `http://localhost` — it has to be HTTPS, and it has to be on the project's real domain, or the cookie silently never lands and the app looks "logged in" for a second then bounces back to logged-out.
13
+
14
+ ## Where the domain comes from
15
+
16
+ `--app-domain` on `blocks new web` is the app's real Blocks origin, e.g. `https://dbpdba.seliseblocks.com`. The generated `.env` keeps two derived values:
17
+
18
+ - `VITE_BLOCKS_APP_DOMAIN` — the full value as passed, with scheme (`https://dbpdba.seliseblocks.com`).
19
+ - `VITE_BLOCKS_DEV_HOST` — the same host with no scheme (`dbpdba.seliseblocks.com`), computed by the scaffold generator and used everywhere locally: hosts file, cert `commonName`/SAN, and `vite.config.ts`'s `server.host`/`allowedHosts`.
20
+
21
+ You never need to look this up separately — it's already sitting in `.env` after scaffolding.
22
+
23
+ ## The flow
24
+
25
+ ```bash
26
+ cd <appName>
27
+ npm install
28
+ npm run cert
29
+ ```
30
+
31
+ `npm run cert` runs `scripts/generate-cert.mjs`, which reads `VITE_BLOCKS_DEV_HOST` from `.env` (or `process.env`, or an explicit `npm run cert -- <domain>` argument) and uses the `selfsigned` npm dependency to write `.cert/dev-key.pem` and `.cert/dev-cert.pem`, with `subjectAltName` covering the domain, `localhost`, and `127.0.0.1`. This is pure Node — it works from a plain PowerShell prompt with no OpenSSL and no Git Bash/WSL switch.
32
+
33
+ Next, map the domain to your machine — add one line to the hosts file (needs admin/elevated):
34
+
35
+ ```text
36
+ 127.0.0.1 dbpdba.seliseblocks.com
37
+ ```
38
+
39
+ Windows path: `C:\Windows\System32\drivers\etc\hosts` (edit as Administrator).
40
+
41
+ Then:
42
+
43
+ ```bash
44
+ npm run dev
45
+ ```
46
+
47
+ `vite.config.ts` picks up `.cert/dev-key.pem` + `.cert/dev-cert.pem` automatically when both exist and serves HTTPS on `VITE_BLOCKS_DEV_HOST`:`VITE_BLOCKS_DEV_PORT` (default port `5173`, `strictPort: true` because the port is baked into the registered OIDC redirect URI). It also sets `allowedHosts` to that domain, working around Vite's default DNS-rebinding protection which otherwise 404s custom hosts with "Blocked request."
48
+
49
+ Finally, open the app at:
50
+
51
+ ```text
52
+ https://dbpdba.seliseblocks.com:5173
53
+ ```
54
+
55
+ **Not** `http://`, **not** `localhost` — either one skips the cookie entirely even though the app loads.
56
+
57
+ ## Trusting the cert (optional but recommended)
58
+
59
+ The cert is self-signed, so the browser shows a one-time warning until trusted. `npm run cert` prints the exact trust command for your OS when it finishes:
60
+
61
+ - Windows (elevated prompt): `certutil -addstore -f Root .cert\dev-cert.pem`
62
+ - macOS: `sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain .cert/dev-cert.pem`
63
+ - Linux: `sudo cp .cert/dev-cert.pem /usr/local/share/ca-certificates/blocks-dev.crt && sudo update-ca-certificates`
64
+
65
+ Restart the browser after trusting so it picks up the new trust store entry. `.cert/` is already gitignored by the scaffold — it's per-machine, never committed.
66
+
67
+ ## Still need a public OIDC client
68
+
69
+ Local HTTPS alone doesn't make login succeed if no OIDC client is registered yet, or if its `redirect_uris` don't include this exact dev origin. That registration is portal-only (see blocks-onboarding's Gotchas) — out of scope here, but it's the next thing to check if HTTPS is right and login still fails. The redirect URI must match byte-for-byte, including `:5173`.
70
+
71
+ ## Gotchas
72
+
73
+ **Custom app domain: the API base URL must share its registrable domain, or the cookie never lands.** `blocks new web --app-domain` isn't limited to `*.seliseblocks.com` — it also supports custom domains (e.g. `abc.slsblx.com`, `xyz.blx10.com`). On a custom domain, the hosted-login session cookie is only stored by the browser if `VITE_BLOCKS_API_URL` shares the app's registrable domain. Concretely:
74
+
75
+ - `abc.slsblx.com` → `VITE_BLOCKS_API_URL` must be `https://blocksapi.slsblx.com`
76
+ - `xyz.blx10.com` → `VITE_BLOCKS_API_URL` must be `https://blocksapi.blx10.com`
77
+
78
+ If `VITE_BLOCKS_API_URL` is left at the default `https://api.seliseblocks.com` while the app itself runs on a custom domain, the browser treats the API as cross-site relative to the app and never stores the cookie — login still redirects back and *looks* successful, but cookie-based calls (`/iam/me`, organization switching, logout) silently fail. Check `VITE_BLOCKS_API_URL` in `.env` first whenever the app domain is not `*.seliseblocks.com` and auth-dependent calls are failing despite HTTPS and the cert being set up correctly.
79
+
80
+ ## Troubleshooting
81
+
82
+ | Symptom | Likely cause | Fix |
83
+ |---|---|---|
84
+ | Login redirects back but app shows logged-out | Opened on `http://` or `localhost` instead of the HTTPS dev host | Check the URL bar: must be `https://<VITE_BLOCKS_DEV_HOST>:5173` exactly |
85
+ | Browser "not private" warning | Self-signed cert not trusted yet | Run the OS trust command `npm run cert` printed, then restart the browser |
86
+ | Vite: "Blocked request. This host is not allowed" | `allowedHosts` doesn't include the host being requested — usually `.env`'s `VITE_BLOCKS_DEV_HOST` was edited/missing after scaffold, or the browser is hitting a different hostname than expected | Confirm `.env` has the right `VITE_BLOCKS_DEV_HOST` and that the URL bar matches it exactly |
87
+ | Page doesn't load at all / DNS error | Hosts file entry missing or wrong | Add `127.0.0.1 <VITE_BLOCKS_DEV_HOST>` to `C:\Windows\System32\drivers\etc\hosts` (as Administrator) |
88
+ | `npm run cert` errors "No domain given" | `.env` missing or `VITE_BLOCKS_DEV_HOST` not set | Confirm `.env` exists with `VITE_BLOCKS_DEV_HOST=<host>`, or run `npm run cert -- <domain>` explicitly |
89
+ | `npm run dev` fails to bind the port | Port 5173 already in use (dev server uses `strictPort`, won't fall back) | Free port 5173, or the port is fixed because it's part of the registered OIDC redirect URI — don't just change it without updating the OIDC client too |
90
+ | HTTPS works, cert trusted, login still fails | No OIDC client registered yet, or its redirect URI doesn't match this origin exactly | Register/update the public OIDC client in the portal with `https://<VITE_BLOCKS_DEV_HOST>:5173/login/callback` as a redirect URI (portal-only, see blocks-onboarding) |
91
+ | Works for one dev, fails for a teammate | Each machine needs its own cert + hosts entry — `.cert/` is gitignored on purpose | Teammate runs `npm run cert` and adds the hosts entry on their own machine |
92
+
93
+ ## Example trigger prompts
94
+
95
+ - "Run my app locally over HTTPS on its real domain so SSO works"
96
+ - "My local login isn't working — it just bounces back to the login page"
97
+ - "How do I set up the dev cert for this scaffolded app?"
98
+ - "I'm getting 'Blocked request. This host is not allowed' from Vite"
99
+ - "Do I need OpenSSL to run npm run cert on Windows?"
100
+ - "Why does login work in production but not on localhost?"