@seliseblocks/cli-os 0.1.2 → 0.1.3
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/AI_USAGE_GUIDE.md +18 -38
- package/README.md +13 -36
- package/dist/commands/auth/status.js +16 -55
- package/dist/commands/doctor.js +3 -4
- package/dist/commands/init.js +4 -2
- package/dist/commands/login.d.ts +0 -1
- package/dist/commands/login.js +15 -55
- package/dist/commands/new/web.js +1 -1
- package/dist/index.js +10 -56
- package/dist/lib/auth.d.ts +4 -11
- package/dist/lib/auth.js +43 -18
- package/dist/lib/config.d.ts +1 -2
- package/dist/lib/config.js +45 -13
- package/dist/lib/open-browser.js +26 -3
- package/dist/lib/secret-store.d.ts +0 -1
- package/dist/lib/secret-store.js +1 -4
- package/dist/lib/token-store.d.ts +0 -1
- package/dist/lib/token-store.js +1 -5
- package/dist/lib/token.d.ts +0 -1
- package/dist/lib/token.js +0 -4
- package/package.json +1 -1
- package/dist/commands/auth/add.d.ts +0 -1
- package/dist/commands/auth/add.js +0 -51
- package/dist/commands/auth/list.d.ts +0 -1
- package/dist/commands/auth/list.js +0 -33
- package/dist/commands/auth/repair.d.ts +0 -1
- package/dist/commands/auth/repair.js +0 -39
- package/dist/commands/auth/show.d.ts +0 -1
- package/dist/commands/auth/show.js +0 -23
- package/dist/commands/auth/use.d.ts +0 -1
- package/dist/commands/auth/use.js +0 -15
- package/dist/commands/login-device.d.ts +0 -1
- package/dist/commands/login-device.js +0 -6
- package/dist/lib/login-server.d.ts +0 -1
- package/dist/lib/login-server.js +0 -43
- package/dist/lib/pkce.d.ts +0 -4
- package/dist/lib/pkce.js +0 -9
- package/dist/lib/prompt.d.ts +0 -2
- package/dist/lib/prompt.js +0 -36
package/AI_USAGE_GUIDE.md
CHANGED
|
@@ -27,58 +27,38 @@ For local package development only, contributors may run `node bin/run.js ...` f
|
|
|
27
27
|
- Prefer `--json` for automation and parsing.
|
|
28
28
|
- Use `--dry-run` before any mutating command.
|
|
29
29
|
- Do not run real mutating cloud commands unless the user explicitly approved the exact action.
|
|
30
|
-
- Never print, commit, scaffold, or document
|
|
30
|
+
- Never print, commit, scaffold, or document access tokens, refresh tokens, cookies, JWTs, or other secrets.
|
|
31
31
|
- Treat any secret pasted into chat or logs as exposed and rotate it before production use.
|
|
32
|
-
- Generated apps must not contain CLI
|
|
32
|
+
- Generated apps must not contain CLI tokens.
|
|
33
33
|
- If a CLI command returns an error, fix or report the CLI path. Do not bypass the CLI with a one-off API request when the command exists.
|
|
34
34
|
|
|
35
|
-
## Account Setup
|
|
36
|
-
|
|
37
|
-
Add the CLI OIDC account:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
blocks-os auth:add --client-id <clientId> --client-secret <clientSecret>
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Normal production defaults are built in:
|
|
44
|
-
|
|
45
|
-
```text
|
|
46
|
-
API URL: https://api.seliseblocks.com
|
|
47
|
-
OIDC URL: https://iam.seliseblocks.com
|
|
48
|
-
OS portal URL: https://os.seliseblocks.com
|
|
49
|
-
Redirect URI: http://127.0.0.1:8976/callback
|
|
50
|
-
Scope: openid profile offline_access
|
|
51
|
-
Root tenant: d7e5554c758541db8a18694b64ef423d
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
The CLI stores client secrets and OAuth tokens in the OS credential backend when available. Do not manually copy token files between machines.
|
|
55
|
-
|
|
56
35
|
## Login
|
|
57
36
|
|
|
58
|
-
|
|
37
|
+
Device-code login uses the packaged OS client id. It prints a verification URL and user code, opens the browser to the verification page when possible, then polls until approved:
|
|
59
38
|
|
|
60
39
|
```bash
|
|
61
40
|
blocks-os login
|
|
62
41
|
```
|
|
63
42
|
|
|
64
|
-
|
|
43
|
+
Check current auth state:
|
|
65
44
|
|
|
66
45
|
```bash
|
|
67
|
-
blocks-os
|
|
46
|
+
blocks-os auth:status --json
|
|
68
47
|
```
|
|
69
48
|
|
|
70
|
-
|
|
49
|
+
If local auth state is stale or corrupted (Windows profile change, machine migration, Keychain reset), clear local auth state and log in again:
|
|
71
50
|
|
|
72
51
|
```bash
|
|
73
|
-
blocks-os auth:
|
|
52
|
+
blocks-os auth:remove <account>
|
|
53
|
+
blocks-os login
|
|
74
54
|
```
|
|
75
55
|
|
|
76
|
-
|
|
56
|
+
Use `blocks-os logout` to revoke the current refresh token when possible and remove local session data. Use `blocks-os auth:refresh --json` to force account token refresh, and `blocks-os auth:refresh --project --json` after a project session already exists.
|
|
57
|
+
|
|
58
|
+
Run health checks without mutation:
|
|
77
59
|
|
|
78
60
|
```bash
|
|
79
|
-
blocks-os
|
|
80
|
-
blocks-os auth:add --client-id <clientId> --client-secret <clientSecret>
|
|
81
|
-
blocks-os login
|
|
61
|
+
blocks-os doctor --json
|
|
82
62
|
```
|
|
83
63
|
|
|
84
64
|
## Project Workflow
|
|
@@ -113,17 +93,15 @@ blocks-os projects:get --json
|
|
|
113
93
|
Generate a React/Vite Blocks app:
|
|
114
94
|
|
|
115
95
|
```bash
|
|
116
|
-
blocks-os new web <appName> --x-blocks-key <projectTenantId> --app-domain <appDomainOrUrl>
|
|
96
|
+
blocks-os new web <appName> --x-blocks-key <projectTenantId> --app-domain <appDomainOrUrl>
|
|
117
97
|
```
|
|
118
98
|
|
|
119
99
|
If a public browser OIDC client exists for the app, include it:
|
|
120
100
|
|
|
121
101
|
```bash
|
|
122
|
-
blocks-os new web <appName> --x-blocks-key <projectTenantId> --app-domain <appDomainOrUrl> --
|
|
102
|
+
blocks-os new web <appName> --x-blocks-key <projectTenantId> --app-domain <appDomainOrUrl> --client-id <publicOidcClientId>
|
|
123
103
|
```
|
|
124
104
|
|
|
125
|
-
Use `--oidc-url <url>` only for non-production IAM authority overrides; it defaults to `https://iam.seliseblocks.com`.
|
|
126
|
-
|
|
127
105
|
Validate the scaffold:
|
|
128
106
|
|
|
129
107
|
```bash
|
|
@@ -132,7 +110,7 @@ npm install
|
|
|
132
110
|
npm run build
|
|
133
111
|
```
|
|
134
112
|
|
|
135
|
-
Do not pass
|
|
113
|
+
Do not pass CLI auth state to the scaffolded app. Browser apps must use a public OIDC client and the SDK hosted IdP flow: `blocksClient.auth.idp.redirectToProvider()` on login click and `blocksClient.auth.idp.callback()` on `/login/callback`.
|
|
136
114
|
|
|
137
115
|
`--app-domain` is the app's real Blocks domain/origin, for example `https://dbpdba.seliseblocks.com`. The generated `.env` keeps that full value as `VITE_BLOCKS_APP_DOMAIN` and derives the local dev host without a scheme as `VITE_BLOCKS_DEV_HOST=dbpdba.seliseblocks.com`.
|
|
138
116
|
|
|
@@ -278,7 +256,9 @@ blocks-os release:builds:list --repo-id <repoId> --json
|
|
|
278
256
|
## Agent Failure Handling
|
|
279
257
|
|
|
280
258
|
- `not_logged_in`: run `blocks-os login`.
|
|
281
|
-
- `
|
|
259
|
+
- `refresh_token_rejected`: run `blocks-os login`.
|
|
260
|
+
- `refresh_network_error`: check the network and configured OIDC URL, then retry.
|
|
261
|
+
- `auth_repair_required`: inspect `blocks-os auth:status --json`; if local storage is unreadable or stale, run `blocks-os auth:remove <account>`, then `blocks-os auth:status --json` and `blocks-os login`.
|
|
282
262
|
- `project_not_selected`: run `blocks-os use <projectTenantId>`.
|
|
283
263
|
- `api_auth_failed`: run `blocks-os auth:status --json`, then login again.
|
|
284
264
|
- HTML returned from an API command means the command endpoint path is wrong and must be fixed in the CLI.
|
package/README.md
CHANGED
|
@@ -14,28 +14,12 @@ npm install -g @seliseblocks/cli-os
|
|
|
14
14
|
blocks-os --version
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
blocks-os auth:add --client-id <cli-client-id> --client-secret <cli-client-secret>
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
The CLI fixes these defaults unless you pass advanced override flags:
|
|
24
|
-
|
|
25
|
-
```text
|
|
26
|
-
API URL: https://api.seliseblocks.com
|
|
27
|
-
OIDC URL: https://iam.seliseblocks.com
|
|
28
|
-
OS portal URL: https://os.seliseblocks.com
|
|
29
|
-
Redirect URI: http://127.0.0.1:8976/callback
|
|
30
|
-
Scope: openid profile offline_access
|
|
31
|
-
Root tenant: d7e5554c758541db8a18694b64ef423d
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Then use either login method with the same account:
|
|
17
|
+
Then log in (device-code flow - prints a verification URL and code, opens
|
|
18
|
+
your browser to the verification page when possible so you only need to
|
|
19
|
+
click approve, then polls until approved):
|
|
35
20
|
|
|
36
21
|
```bash
|
|
37
22
|
blocks-os login
|
|
38
|
-
blocks-os login:device
|
|
39
23
|
```
|
|
40
24
|
|
|
41
25
|
For source development in this repository:
|
|
@@ -52,16 +36,10 @@ node bin/run.js --version
|
|
|
52
36
|
|---|---|
|
|
53
37
|
| `blocks-os init` | Create local Blocks workspace files: `blocks.json`, data schema/rules folders, release deploy config, and `.env.example`. |
|
|
54
38
|
| `blocks-os doctor [--json]` | Check Node.js, OIDC config, token cache, selected project, and config file locations. Does not mutate cloud resources. |
|
|
55
|
-
| `blocks-os
|
|
56
|
-
| `blocks-os
|
|
57
|
-
| `blocks-os login:device` | Device-code login for terminals or agents where a browser callback is not convenient. Uses the same saved account profile. |
|
|
58
|
-
| `blocks-os auth:status [--json]` | Show account config, token freshness, refresh-token availability, and selected project without printing secrets. |
|
|
39
|
+
| `blocks-os login` | Device-code login. Prints a verification URL and user code, opens the browser to the verification page when possible so you only need to click approve, then polls until the device is authorized; stores account tokens and auto-refreshes later. |
|
|
40
|
+
| `blocks-os auth:status [--json]` | Show only whether account/project access and refresh tokens are missing, valid, expired, or available. Does not print account config values. |
|
|
59
41
|
| `blocks-os auth:refresh [--project] [--json]` | Force account token refresh, or project token refresh with `--project`. |
|
|
60
|
-
| `blocks-os auth:
|
|
61
|
-
| `blocks-os auth:list [--json]` | List saved account profiles without showing secrets. |
|
|
62
|
-
| `blocks-os auth:show [account]` | Print one account profile with secret redacted as `configured` or `missing`. |
|
|
63
|
-
| `blocks-os auth:use <account>` | Switch the active named account profile. Normal single-account use does not need this. |
|
|
64
|
-
| `blocks-os auth:remove <account>` | Remove account config, cached tokens, and stored client secret for that account. |
|
|
42
|
+
| `blocks-os auth:remove <account>` | Clear cached tokens and stored local credentials for that account. The packaged default OS account is restored from package defaults. |
|
|
65
43
|
| `blocks-os logout` | Revoke the current refresh token when possible and remove local session data. |
|
|
66
44
|
| `blocks-os projects:list [--json]` | List accessible Blocks projects via `/os/v4/Project/Gets` using the account token. Read-only. |
|
|
67
45
|
| `blocks-os projects:get [tenantId] [--json]` | Read one project from `Project/Gets`. Uses selected project when `tenantId` is omitted. Read-only. |
|
|
@@ -82,7 +60,7 @@ node bin/run.js --version
|
|
|
82
60
|
| `blocks-os release:status <buildId> [--json]` | Read Release build status by build id. Read-only. |
|
|
83
61
|
| `blocks-os release:builds:list --repo-id <repoId> [--json]` | List Release build details for a repository. Read-only. |
|
|
84
62
|
| `blocks-os release:builds:get <buildId> [--json]` | Alias for `release:status`. Read-only. |
|
|
85
|
-
| `blocks-os new web <name> --x-blocks-key <tenantId> --app-domain <domain-or-url>
|
|
63
|
+
| `blocks-os new web <name> --x-blocks-key <tenantId> --app-domain <domain-or-url> [--client-id <oidcClientId>]` | Create a Vite React starter app that talks to Blocks exclusively through `@seliseblocks/client` (a single `createBlocksClient()` instance) using the SDK hosted IdP flow: `blocksClient.auth.idp.redirectToProvider()` on login click and `blocksClient.auth.idp.callback()` on `/login/callback`. Includes route guards, auto-refresh through `auth.oidc.refreshToken()`, live `auth`/`iam`/`data`/`localization` SDK examples, environment config, and safe `.gitignore` defaults. Register a **public** OIDC client with redirect URIs for both your local HTTPS dev origin and `--app-domain`, then pass its id as `--client-id` (or set `VITE_BLOCKS_OIDC_CLIENT_ID` in `.env` afterwards). |
|
|
86
64
|
|
|
87
65
|
Use `--json` on commands when AI or automation needs machine-readable output. Use `--dry-run` before mutations and `--yes` only after approval.
|
|
88
66
|
|
|
@@ -109,24 +87,23 @@ Add the generated `VITE_BLOCKS_DEV_HOST` to your hosts file, for example:
|
|
|
109
87
|
|
|
110
88
|
Then open `https://<VITE_BLOCKS_DEV_HOST>:5173`, not plain `http://`. The generated cert script uses a Node dependency, so it works from normal PowerShell after `npm install`; OpenSSL/Git Bash is not required.
|
|
111
89
|
|
|
112
|
-
OIDC account settings are saved in the OS-specific config directory. Access and refresh tokens are stored in
|
|
90
|
+
OIDC account settings are saved in the OS-specific config directory. Access and refresh tokens are stored in an OS-aware secure backend when available, and are refreshed automatically before cloud commands when a refresh token is available.
|
|
113
91
|
|
|
114
|
-
|
|
92
|
+
OAuth tokens use an OS-aware credential backend:
|
|
115
93
|
|
|
116
|
-
| OS |
|
|
94
|
+
| OS | Credential backend |
|
|
117
95
|
|---|---|
|
|
118
96
|
| Windows | DPAPI-encrypted value in the CLI secret metadata file, scoped to the current Windows user. |
|
|
119
97
|
| macOS | Keychain generic password for the `seliseblocks-cli-os` service. |
|
|
120
98
|
| Linux | Secret Service through `secret-tool` when available. |
|
|
121
99
|
| Fallback | `0600` file storage in the CLI config directory. Set `BLOCKS_OS_SECRET_STORE=file` to force this mode for CI or minimal containers. |
|
|
122
100
|
|
|
123
|
-
Use `blocks-os doctor` or `blocks-os auth:status` to see which backend is active.
|
|
101
|
+
Use `blocks-os doctor` or `blocks-os auth:status` to see which backend is active. Tokens are never printed by CLI status commands.
|
|
124
102
|
|
|
125
|
-
If the active OS credential backend cannot decrypt old local auth state after a Windows profile change, machine migration, Keychain reset, or corrupted token cache,
|
|
103
|
+
If the active OS credential backend cannot decrypt old local auth state after a Windows profile change, machine migration, Keychain reset, or corrupted token cache, clear local auth state and log in again:
|
|
126
104
|
|
|
127
105
|
```bash
|
|
128
|
-
blocks-os auth:
|
|
129
|
-
blocks-os auth:add --client-id <cli-client-id> --client-secret <cli-client-secret>
|
|
106
|
+
blocks-os auth:remove <account>
|
|
130
107
|
blocks-os login
|
|
131
108
|
```
|
|
132
109
|
|
|
@@ -1,75 +1,36 @@
|
|
|
1
1
|
import { parseFlags, stringFlag } from "../../lib/args.js";
|
|
2
2
|
import { getAccountProfile, readConfig } from "../../lib/config.js";
|
|
3
3
|
import { isExpiring } from "../../lib/token.js";
|
|
4
|
-
import { readTokenStore
|
|
4
|
+
import { readTokenStore } from "../../lib/token-store.js";
|
|
5
5
|
import { writeOutput } from "../../lib/output.js";
|
|
6
|
-
import { getClientSecret, secretStoreInfo } from "../../lib/secret-store.js";
|
|
7
6
|
export async function authStatus(argv = []) {
|
|
8
7
|
const { flags } = parseFlags(argv);
|
|
9
8
|
const config = await readConfig();
|
|
10
9
|
const store = await readTokenStore();
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
if (Object.keys(config.accounts).length === 0) {
|
|
14
|
-
if (flags.json) {
|
|
15
|
-
writeOutput({ configured: false, message: "No OIDC accounts configured." }, flags);
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
console.log("No OIDC accounts configured. Run 'blocks-os auth:add --client-id <id> --client-secret <secret>'.");
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
const { name, profile } = getAccountProfile(config, accountOverride);
|
|
22
|
-
const clientSecret = await getClientSecret(name);
|
|
23
|
-
const secretInfo = await secretStoreInfo();
|
|
10
|
+
const accountOverride = stringFlag(flags, "account") || undefined;
|
|
11
|
+
const { name } = getAccountProfile(config, accountOverride);
|
|
24
12
|
const accountToken = store.accounts[name]?.account;
|
|
25
13
|
const projectToken = config.selectedProject?.tenantId
|
|
26
14
|
? store.accounts[name]?.projects?.[config.selectedProject.tenantId]
|
|
27
15
|
: undefined;
|
|
28
16
|
if (flags.json) {
|
|
29
17
|
writeOutput({
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
clientId: profile.clientId,
|
|
35
|
-
clientSecret: clientSecret ? "configured" : "missing",
|
|
36
|
-
secretStorage: secretInfo,
|
|
37
|
-
tokenStorage: tokenInfo,
|
|
38
|
-
oidcUrl: profile.oidcUrl,
|
|
39
|
-
osUrl: profile.osUrl,
|
|
40
|
-
scope: profile.scope,
|
|
41
|
-
rootTenant: profile.rootTenantId ?? accountToken?.accountTenant ?? null,
|
|
42
|
-
accountAccessToken: accountToken?.accessToken ? tokenState(accountToken.expiresAt) : "missing",
|
|
43
|
-
accountRefreshToken: accountToken?.refreshToken ? "available" : "missing",
|
|
44
|
-
accountRefreshTokenExpiresAt: accountToken?.refreshTokenExpiresAt ?? null,
|
|
45
|
-
selectedProject: config.selectedProject?.tenantId ?? null,
|
|
46
|
-
projectAccessToken: projectToken?.accessToken ? tokenState(projectToken.expiresAt) : "not created",
|
|
47
|
-
projectRefreshToken: projectToken?.refreshToken ? "available" : "missing",
|
|
48
|
-
projectRefreshTokenExpiresAt: projectToken?.refreshTokenExpiresAt ?? null
|
|
18
|
+
accountAccessToken: tokenState(accountToken?.accessToken, accountToken?.expiresAt),
|
|
19
|
+
accountRefreshToken: tokenState(accountToken?.refreshToken, accountToken?.refreshTokenExpiresAt),
|
|
20
|
+
projectAccessToken: tokenState(projectToken?.accessToken, projectToken?.expiresAt),
|
|
21
|
+
projectRefreshToken: tokenState(projectToken?.refreshToken, projectToken?.refreshTokenExpiresAt)
|
|
49
22
|
}, flags);
|
|
50
23
|
return;
|
|
51
24
|
}
|
|
52
|
-
console.log(`
|
|
53
|
-
console.log(`Account: ${
|
|
54
|
-
console.log(`
|
|
55
|
-
console.log(`
|
|
56
|
-
console.log(`OS URL: ${profile.osUrl}`);
|
|
57
|
-
console.log(`OIDC client id: ${profile.clientId}`);
|
|
58
|
-
console.log(`OIDC client secret: ${clientSecret ? "configured" : "missing"}`);
|
|
59
|
-
console.log(`Secret storage: ${secretInfo.backend} (${secretInfo.detail})`);
|
|
60
|
-
console.log(`Token storage: ${tokenInfo.backend} (${tokenInfo.detail})`);
|
|
61
|
-
console.log(`Scope: ${profile.scope}`);
|
|
62
|
-
console.log(`Root tenant: ${profile.rootTenantId ?? accountToken?.accountTenant ?? "missing"}`);
|
|
63
|
-
console.log(`Account access token: ${accountToken?.accessToken ? tokenState(accountToken.expiresAt) : "missing"}`);
|
|
64
|
-
console.log(`Account refresh token: ${accountToken?.refreshToken ? "available" : "missing"}`);
|
|
65
|
-
console.log(`Account refresh token expires: ${accountToken?.refreshTokenExpiresAt ?? "unknown"}`);
|
|
66
|
-
console.log(`Selected project: ${config.selectedProject?.tenantId ?? "not selected"}`);
|
|
67
|
-
console.log(`Project access token: ${projectToken?.accessToken ? tokenState(projectToken.expiresAt) : "not created"}`);
|
|
68
|
-
console.log(`Project refresh token: ${projectToken?.refreshToken ? "available" : "missing"}`);
|
|
69
|
-
console.log(`Project refresh token expires: ${projectToken?.refreshTokenExpiresAt ?? "unknown"}`);
|
|
25
|
+
console.log(`Account access token: ${tokenState(accountToken?.accessToken, accountToken?.expiresAt)}`);
|
|
26
|
+
console.log(`Account refresh token: ${tokenState(accountToken?.refreshToken, accountToken?.refreshTokenExpiresAt)}`);
|
|
27
|
+
console.log(`Project access token: ${tokenState(projectToken?.accessToken, projectToken?.expiresAt)}`);
|
|
28
|
+
console.log(`Project refresh token: ${tokenState(projectToken?.refreshToken, projectToken?.refreshTokenExpiresAt)}`);
|
|
70
29
|
}
|
|
71
|
-
function tokenState(expiresAt) {
|
|
30
|
+
function tokenState(token, expiresAt) {
|
|
31
|
+
if (!token)
|
|
32
|
+
return "missing";
|
|
72
33
|
if (!expiresAt)
|
|
73
|
-
return "
|
|
74
|
-
return
|
|
34
|
+
return "available";
|
|
35
|
+
return isExpiring(expiresAt) ? "expired" : "valid";
|
|
75
36
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -5,7 +5,7 @@ import { parseFlags } from "../lib/args.js";
|
|
|
5
5
|
import { writeOutput } from "../lib/output.js";
|
|
6
6
|
import { isExpiring } from "../lib/token.js";
|
|
7
7
|
import { readTokenStore, tokenPath, tokenStoreInfo } from "../lib/token-store.js";
|
|
8
|
-
import {
|
|
8
|
+
import { secretPath, secretStoreInfo } from "../lib/secret-store.js";
|
|
9
9
|
export async function doctor(argv = []) {
|
|
10
10
|
const { flags } = parseFlags(argv);
|
|
11
11
|
const config = await readConfig();
|
|
@@ -17,8 +17,7 @@ export async function doctor(argv = []) {
|
|
|
17
17
|
{ label: "OIDC account configured", ok: Object.keys(config.accounts).length > 0, detail: config.activeAccount ?? "missing" }
|
|
18
18
|
];
|
|
19
19
|
if (Object.keys(config.accounts).length > 0) {
|
|
20
|
-
const { name
|
|
21
|
-
const clientSecret = await getClientSecret(name);
|
|
20
|
+
const { name } = getAccountProfile(config);
|
|
22
21
|
const secretInfo = await secretStoreInfo();
|
|
23
22
|
const accountToken = store.accounts[name]?.account;
|
|
24
23
|
let sessionValid = false;
|
|
@@ -38,7 +37,7 @@ export async function doctor(argv = []) {
|
|
|
38
37
|
const projectToken = config.selectedProject?.tenantId
|
|
39
38
|
? store.accounts[name]?.projects?.[config.selectedProject.tenantId]
|
|
40
39
|
: undefined;
|
|
41
|
-
checks.push({ label: "
|
|
40
|
+
checks.push({ label: "Credential storage backend", ok: true, detail: `${secretInfo.backend} (${secretInfo.detail})` }, { label: "Account session", ok: sessionValid, detail: sessionDetail }, { label: "Account refresh token", ok: Boolean(refreshedAccountToken?.refreshToken), detail: refreshedAccountToken?.refreshToken ? "available" : "missing" }, { label: "Account access token cached", ok: Boolean(refreshedAccountToken?.accessToken && !isExpiring(refreshedAccountToken.expiresAt)), detail: refreshedAccountToken?.expiresAt ?? "missing" }, { label: "Project selected", ok: Boolean(config.selectedProject?.tenantId), detail: config.selectedProject?.tenantId ?? "missing" }, { label: "Project access token cached", ok: Boolean(!projectToken?.accessToken || !isExpiring(projectToken.expiresAt)), detail: projectToken?.expiresAt ?? "not created yet" });
|
|
42
41
|
}
|
|
43
42
|
let configFile = "missing";
|
|
44
43
|
try {
|
package/dist/commands/init.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { defaults } from "../lib/config.js";
|
|
3
4
|
export async function init() {
|
|
5
|
+
const env = defaults();
|
|
4
6
|
await mkdir(join(process.cwd(), "blocks", "data", "schemas"), { recursive: true });
|
|
5
7
|
await mkdir(join(process.cwd(), "blocks", "localization"), { recursive: true });
|
|
6
8
|
await mkdir(join(process.cwd(), "blocks", "release"), { recursive: true });
|
|
7
9
|
await writeIfMissing("blocks.json", `${JSON.stringify({
|
|
8
10
|
project: {
|
|
9
11
|
tenantId: "",
|
|
10
|
-
apiUrl:
|
|
12
|
+
apiUrl: env.apiUrl,
|
|
11
13
|
appDomain: ""
|
|
12
14
|
},
|
|
13
15
|
data: {
|
|
@@ -29,7 +31,7 @@ export async function init() {
|
|
|
29
31
|
strategy: "configured-pipeline"
|
|
30
32
|
}, null, 2)}\n`);
|
|
31
33
|
await writeIfMissing(".env.example", [
|
|
32
|
-
|
|
34
|
+
`VITE_BLOCKS_API_URL=${env.apiUrl}`,
|
|
33
35
|
"VITE_BLOCKS_X_BLOCKS_KEY=",
|
|
34
36
|
"VITE_BLOCKS_APP_DOMAIN=",
|
|
35
37
|
""
|
package/dist/commands/login.d.ts
CHANGED
package/dist/commands/login.js
CHANGED
|
@@ -1,75 +1,35 @@
|
|
|
1
|
-
import { URLSearchParams } from "node:url";
|
|
2
1
|
import { parseFlags, stringFlag } from "../lib/args.js";
|
|
3
|
-
import {
|
|
2
|
+
import { pollDeviceToken, requestDeviceAuthorization } from "../lib/auth.js";
|
|
4
3
|
import { getAccountProfile, readConfig, writeConfig } from "../lib/config.js";
|
|
5
|
-
import { waitForAuthorizationCode } from "../lib/login-server.js";
|
|
6
4
|
import { openBrowser } from "../lib/open-browser.js";
|
|
7
|
-
import {
|
|
8
|
-
import { applyAccountToken, randomState } from "../lib/token.js";
|
|
5
|
+
import { applyAccountToken } from "../lib/token.js";
|
|
9
6
|
import { readTokenStore, writeTokenStore } from "../lib/token-store.js";
|
|
10
|
-
import { getClientSecret } from "../lib/secret-store.js";
|
|
11
7
|
export async function login(argv) {
|
|
12
8
|
const { flags } = parseFlags(argv);
|
|
13
9
|
const accountOverride = stringFlag(flags, "account");
|
|
14
|
-
const config = await readConfig();
|
|
15
|
-
const { name, profile } = getAccountProfile(config, accountOverride);
|
|
16
|
-
const redirectUri = profile.redirectUri;
|
|
17
|
-
if (!redirectUri) {
|
|
18
|
-
throw new Error("OIDC redirectUri is missing. Run 'blocks-os auth:add --client-id <id> --client-secret <secret>' to restore the default redirect URI.");
|
|
19
|
-
}
|
|
20
|
-
const url = new URL(redirectUri);
|
|
21
|
-
const port = Number(url.port || (url.protocol === "https:" ? 443 : 80));
|
|
22
|
-
const state = randomState();
|
|
23
|
-
const { challenge, verifier } = createPkcePair();
|
|
24
|
-
const authUrl = new URL("/api/oidc/authorize", profile.oidcUrl);
|
|
25
|
-
authUrl.search = new URLSearchParams({
|
|
26
|
-
client_id: profile.clientId,
|
|
27
|
-
code_challenge: challenge,
|
|
28
|
-
code_challenge_method: "S256",
|
|
29
|
-
redirect_uri: redirectUri,
|
|
30
|
-
response_type: "code",
|
|
31
|
-
scope: profile.scope,
|
|
32
|
-
state,
|
|
33
|
-
tenant_id: profile.rootTenantId ?? ""
|
|
34
|
-
}).toString();
|
|
35
|
-
const codePromise = waitForAuthorizationCode(port, state);
|
|
36
|
-
console.log("Open this URL to log in to Blocks OS:");
|
|
37
|
-
console.log(authUrl.toString());
|
|
38
|
-
const opened = await openBrowser(authUrl.toString());
|
|
39
|
-
if (!opened) {
|
|
40
|
-
console.log("Browser auto-open is unavailable on this machine. Open the URL manually.");
|
|
41
|
-
}
|
|
42
|
-
const code = await codePromise;
|
|
43
|
-
const token = await exchangeAuthorizationCode({
|
|
44
|
-
account: name,
|
|
45
|
-
clientId: profile.clientId,
|
|
46
|
-
clientSecret: await getClientSecret(name),
|
|
47
|
-
code,
|
|
48
|
-
codeVerifier: verifier,
|
|
49
|
-
oidcUrl: profile.oidcUrl,
|
|
50
|
-
redirectUri,
|
|
51
|
-
rootTenantId: profile.rootTenantId
|
|
52
|
-
});
|
|
53
|
-
const latest = await readConfig();
|
|
54
|
-
const latestStore = await readTokenStore();
|
|
55
|
-
const next = applyAccountToken(latest, latestStore, name, profile.clientId, token);
|
|
56
|
-
await writeConfig(next.config);
|
|
57
|
-
await writeTokenStore(next.store);
|
|
58
|
-
console.log(`Logged in to account '${name}' for tenant ${next.store.accounts[name].account.accountTenant}`);
|
|
59
|
-
}
|
|
60
|
-
export async function loginWithDeviceProfile(accountOverride) {
|
|
61
10
|
const config = await readConfig();
|
|
62
11
|
const { name, profile } = getAccountProfile(config, accountOverride);
|
|
63
12
|
const device = await requestDeviceAuthorization(profile);
|
|
64
13
|
console.log("Authorize this device:");
|
|
65
14
|
console.log(`URL: ${device.verification_uri_complete ?? device.verification_uri}`);
|
|
66
15
|
console.log(`Code: ${device.user_code}`);
|
|
16
|
+
if (device.verification_uri_complete) {
|
|
17
|
+
const opened = await openBrowser(device.verification_uri_complete);
|
|
18
|
+
console.log(opened
|
|
19
|
+
? "Opened your browser to approve this device -- just confirm the code above."
|
|
20
|
+
: "Browser auto-open is unavailable on this machine. Open the URL above manually.");
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.log("Open the URL above and enter the code to approve this device.");
|
|
24
|
+
}
|
|
67
25
|
console.log("Waiting for approval...");
|
|
68
|
-
const token = await pollDeviceToken(profile, device
|
|
26
|
+
const token = await pollDeviceToken(profile, device, {
|
|
27
|
+
onWait: (seconds) => console.log(`Checking for approval in ${seconds}s...`)
|
|
28
|
+
});
|
|
69
29
|
const latest = await readConfig();
|
|
70
30
|
const latestStore = await readTokenStore();
|
|
71
31
|
const next = applyAccountToken(latest, latestStore, name, profile.clientId, token);
|
|
72
32
|
await writeConfig(next.config);
|
|
73
33
|
await writeTokenStore(next.store);
|
|
74
|
-
console.log(
|
|
34
|
+
console.log("Login done.");
|
|
75
35
|
}
|
package/dist/commands/new/web.js
CHANGED
|
@@ -8,7 +8,7 @@ export async function newWeb(argv) {
|
|
|
8
8
|
if (!name)
|
|
9
9
|
throw new Error("Missing web app name.");
|
|
10
10
|
const appDomain = stringFlag(parsed.flags, "app-domain", { required: true });
|
|
11
|
-
const apiUrl = stringFlag(parsed.flags, "blocks-api-url", { defaultValue:
|
|
11
|
+
const apiUrl = stringFlag(parsed.flags, "blocks-api-url", { defaultValue: defaults().apiUrl });
|
|
12
12
|
const oidcUrl = stringFlag(parsed.flags, "oidc-url", { defaultValue: defaults().oidcUrl });
|
|
13
13
|
const xBlocksKey = stringFlag(parsed.flags, "x-blocks-key", { required: true });
|
|
14
14
|
const oidcClientId = stringFlag(parsed.flags, "client-id");
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import { authAdd } from "./commands/auth/add.js";
|
|
2
|
-
import { authList } from "./commands/auth/list.js";
|
|
3
1
|
import { authRefresh } from "./commands/auth/refresh.js";
|
|
4
|
-
import { authRepair } from "./commands/auth/repair.js";
|
|
5
2
|
import { authRemove } from "./commands/auth/remove.js";
|
|
6
|
-
import { authShow } from "./commands/auth/show.js";
|
|
7
3
|
import { authStatus } from "./commands/auth/status.js";
|
|
8
|
-
import { authUse } from "./commands/auth/use.js";
|
|
9
4
|
import { doctor } from "./commands/doctor.js";
|
|
10
5
|
import { dataReload } from "./commands/data/reload.js";
|
|
11
6
|
import { dataValidate } from "./commands/data/validate.js";
|
|
@@ -20,7 +15,6 @@ import { localizationPull } from "./commands/localization/pull.js";
|
|
|
20
15
|
import { localizationPush } from "./commands/localization/push.js";
|
|
21
16
|
import { localizationValidate } from "./commands/localization/validate.js";
|
|
22
17
|
import { login } from "./commands/login.js";
|
|
23
|
-
import { loginDevice } from "./commands/login-device.js";
|
|
24
18
|
import { logout } from "./commands/logout.js";
|
|
25
19
|
import { newWeb } from "./commands/new/web.js";
|
|
26
20
|
import { createProject } from "./commands/projects/create.js";
|
|
@@ -40,18 +34,6 @@ try {
|
|
|
40
34
|
else if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
41
35
|
printHelp();
|
|
42
36
|
}
|
|
43
|
-
else if (command === "auth:add" || (command === "auth" && subcommand === "add")) {
|
|
44
|
-
await authAdd(command === "auth" ? rest : [subcommand, ...rest].filter(Boolean));
|
|
45
|
-
}
|
|
46
|
-
else if (command === "auth:list" || (command === "auth" && subcommand === "list")) {
|
|
47
|
-
await authList(command === "auth" ? rest : [subcommand, ...rest].filter(Boolean));
|
|
48
|
-
}
|
|
49
|
-
else if (command === "auth:use" || (command === "auth" && subcommand === "use")) {
|
|
50
|
-
await authUse(command === "auth" ? rest : [subcommand, ...rest].filter(Boolean));
|
|
51
|
-
}
|
|
52
|
-
else if (command === "auth:show" || (command === "auth" && subcommand === "show")) {
|
|
53
|
-
await authShow(command === "auth" ? rest : [subcommand, ...rest].filter(Boolean));
|
|
54
|
-
}
|
|
55
37
|
else if (command === "auth:remove" || (command === "auth" && subcommand === "remove")) {
|
|
56
38
|
await authRemove(command === "auth" ? rest : [subcommand, ...rest].filter(Boolean));
|
|
57
39
|
}
|
|
@@ -61,27 +43,18 @@ try {
|
|
|
61
43
|
else if (command === "auth:refresh") {
|
|
62
44
|
await authRefresh([subcommand, ...rest].filter(Boolean));
|
|
63
45
|
}
|
|
64
|
-
else if (command === "auth:repair") {
|
|
65
|
-
await authRepair([subcommand, ...rest].filter(Boolean));
|
|
66
|
-
}
|
|
67
46
|
else if (command === "auth" && subcommand === "status") {
|
|
68
47
|
await authStatus(rest);
|
|
69
48
|
}
|
|
70
49
|
else if (command === "auth" && subcommand === "refresh") {
|
|
71
50
|
await authRefresh(rest);
|
|
72
51
|
}
|
|
73
|
-
else if (command === "auth" && subcommand === "repair") {
|
|
74
|
-
await authRepair(rest);
|
|
75
|
-
}
|
|
76
52
|
else if (command === "doctor") {
|
|
77
53
|
await doctor([subcommand, ...rest].filter(Boolean));
|
|
78
54
|
}
|
|
79
55
|
else if (command === "init") {
|
|
80
56
|
await init();
|
|
81
57
|
}
|
|
82
|
-
else if (command === "login:device") {
|
|
83
|
-
await loginDevice([subcommand, ...rest].filter(Boolean));
|
|
84
|
-
}
|
|
85
58
|
else if (command === "login") {
|
|
86
59
|
await login([subcommand, ...rest].filter(Boolean));
|
|
87
60
|
}
|
|
@@ -181,7 +154,7 @@ function toCliError(error) {
|
|
|
181
154
|
return { code: "not_logged_in", message, nextStep: "blocks-os login" };
|
|
182
155
|
}
|
|
183
156
|
if (message.includes("token expired") || message.includes("refresh token") || message.includes("cannot decrypt")) {
|
|
184
|
-
return { code: "auth_repair_required", message, nextStep: "blocks-os
|
|
157
|
+
return { code: "auth_repair_required", message, nextStep: "blocks-os login" };
|
|
185
158
|
}
|
|
186
159
|
if (message.includes("No project selected")) {
|
|
187
160
|
return { code: "project_not_selected", message, nextStep: "blocks-os use <tenantId>" };
|
|
@@ -216,41 +189,22 @@ Setup and health:
|
|
|
216
189
|
file locations. Does not mutate cloud resources.
|
|
217
190
|
|
|
218
191
|
Auth:
|
|
219
|
-
blocks-os auth:add [account] --client-id <id> --client-secret <secret>
|
|
220
|
-
Save OIDC client settings for the CLI. Defaults target production Blocks
|
|
221
|
-
URLs, root tenant, scope, and localhost callback. Secret is stored separately.
|
|
222
|
-
|
|
223
192
|
blocks-os login
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
Device-code login for terminals or agents where a browser callback is not
|
|
229
|
-
convenient. Uses the same saved account profile.
|
|
193
|
+
Device-code login. Prints a verification URL and user code, opens the
|
|
194
|
+
browser to the verification page when possible so you only need to click
|
|
195
|
+
approve, then polls until the device is authorized; stores account access
|
|
196
|
+
and refresh tokens and auto-refreshes later.
|
|
230
197
|
|
|
231
198
|
blocks-os auth:status [--json]
|
|
232
|
-
Show
|
|
233
|
-
|
|
199
|
+
Show only whether account/project access and refresh tokens are missing,
|
|
200
|
+
valid, expired, or available. Does not print account config values.
|
|
234
201
|
|
|
235
202
|
blocks-os auth:refresh [--project] [--json]
|
|
236
203
|
Force account token refresh, or project token refresh with --project.
|
|
237
204
|
|
|
238
|
-
blocks-os auth:repair [account] [--dry-run] [--yes] [--json]
|
|
239
|
-
Clear broken local secure-store auth state while keeping account profile
|
|
240
|
-
config. Use after OS credential migration, stale DPAPI, or corrupted tokens.
|
|
241
|
-
|
|
242
|
-
blocks-os auth:list [--json]
|
|
243
|
-
List saved account profiles without showing secrets.
|
|
244
|
-
|
|
245
|
-
blocks-os auth:show [account]
|
|
246
|
-
Print one account profile with secret redacted as configured/missing.
|
|
247
|
-
|
|
248
|
-
blocks-os auth:use <account>
|
|
249
|
-
Switch the active named account profile.
|
|
250
|
-
|
|
251
205
|
blocks-os auth:remove <account>
|
|
252
|
-
|
|
253
|
-
account.
|
|
206
|
+
Clear cached tokens and stored local credentials for that account. The
|
|
207
|
+
packaged default OS account is restored from package defaults.
|
|
254
208
|
|
|
255
209
|
blocks-os logout
|
|
256
210
|
Revoke the current refresh token when possible and remove local session data.
|
|
@@ -334,7 +288,7 @@ Release:
|
|
|
334
288
|
Alias for release:status. Read-only.
|
|
335
289
|
|
|
336
290
|
Scaffold:
|
|
337
|
-
blocks-os new web <name> --x-blocks-key <tenantId> --app-domain <domain>
|
|
291
|
+
blocks-os new web <name> --x-blocks-key <tenantId> --app-domain <domain> [--client-id <oidcClientId>]
|
|
338
292
|
Create a Vite React starter app with a real browser Authorization Code +
|
|
339
293
|
PKCE login flow against Blocks IAM (login page, /login/callback handler,
|
|
340
294
|
route guards), plus Data schema listing, Release build lookup, environment
|
package/dist/lib/auth.d.ts
CHANGED
|
@@ -20,18 +20,11 @@ export type DeviceAuthorizationResponse = {
|
|
|
20
20
|
verification_uri: string;
|
|
21
21
|
verification_uri_complete?: string;
|
|
22
22
|
};
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
clientSecret?: string;
|
|
27
|
-
code: string;
|
|
28
|
-
codeVerifier?: string;
|
|
29
|
-
oidcUrl: string;
|
|
30
|
-
redirectUri: string;
|
|
31
|
-
rootTenantId?: string;
|
|
32
|
-
}): Promise<TokenResponse>;
|
|
23
|
+
export type DevicePollingOptions = {
|
|
24
|
+
onWait?: (seconds: number) => void;
|
|
25
|
+
};
|
|
33
26
|
export declare function requestDeviceAuthorization(profile: AccountProfile): Promise<DeviceAuthorizationResponse>;
|
|
34
|
-
export declare function pollDeviceToken(profile: AccountProfile, device: DeviceAuthorizationResponse): Promise<TokenResponse>;
|
|
27
|
+
export declare function pollDeviceToken(profile: AccountProfile, device: DeviceAuthorizationResponse, options?: DevicePollingOptions): Promise<TokenResponse>;
|
|
35
28
|
export declare function getAccountSession(accountOverride?: string): Promise<AccountSession>;
|
|
36
29
|
export declare function selectProject(tenantId: string): Promise<void>;
|
|
37
30
|
export declare function getImpersonatedProjectSession(accountOverride?: string, tenantOverride?: string): Promise<ProjectSession>;
|