@burdenoff/bigconsole-cli 2026.802.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/LICENSE +9 -0
- package/README.md +235 -0
- package/dist/auth/device-code.d.ts +66 -0
- package/dist/auth/device-code.js +239 -0
- package/dist/auth/store.d.ts +46 -0
- package/dist/auth/store.js +90 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +126 -0
- package/dist/commands/auth.d.ts +5 -0
- package/dist/commands/auth.js +352 -0
- package/dist/commands/base.d.ts +47 -0
- package/dist/commands/base.js +100 -0
- package/dist/commands/cli-config.d.ts +6 -0
- package/dist/commands/cli-config.js +79 -0
- package/dist/commands/dashboard.d.ts +5 -0
- package/dist/commands/dashboard.js +220 -0
- package/dist/commands/datasink.d.ts +5 -0
- package/dist/commands/datasink.js +175 -0
- package/dist/commands/drilldown.d.ts +9 -0
- package/dist/commands/drilldown.js +47 -0
- package/dist/commands/index.d.ts +13 -0
- package/dist/commands/index.js +13 -0
- package/dist/commands/parser.d.ts +5 -0
- package/dist/commands/parser.js +181 -0
- package/dist/commands/pipeline.d.ts +5 -0
- package/dist/commands/pipeline.js +142 -0
- package/dist/commands/prompt.d.ts +5 -0
- package/dist/commands/prompt.js +84 -0
- package/dist/commands/share.d.ts +8 -0
- package/dist/commands/share.js +50 -0
- package/dist/commands/template.d.ts +5 -0
- package/dist/commands/template.js +62 -0
- package/dist/commands/widget.d.ts +5 -0
- package/dist/commands/widget.js +164 -0
- package/dist/commands/workflow.d.ts +9 -0
- package/dist/commands/workflow.js +54 -0
- package/dist/commands/workspace.d.ts +9 -0
- package/dist/commands/workspace.js +95 -0
- package/dist/config/index.d.ts +72 -0
- package/dist/config/index.js +151 -0
- package/dist/graphql/client.d.ts +26 -0
- package/dist/graphql/client.js +274 -0
- package/dist/graphql/operations.d.ts +48 -0
- package/dist/graphql/operations.js +654 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/types/index.d.ts +30 -0
- package/dist/types/index.js +7 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/output.d.ts +62 -0
- package/dist/utils/output.js +186 -0
- package/package.json +116 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Copyright (c) 2025 Burdenoff Consultancy Services Private Limited, Algoshred Technologies Private Limited and all sister companies.
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is proprietary and confidential. Unauthorized copying, modification, distribution, or use of this software, via any medium, is strictly prohibited without the express written permission of the copyright holders.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
8
|
+
|
|
9
|
+
For licensing inquiries, please contact: legal@algoshred.com
|
package/README.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Bigconsole Cli
|
|
2
|
+
|
|
3
|
+
A powerful command-line interface for managing bigconsole operations with GraphQL backend integration.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔐 **Authentication Management** - Login, logout, and status checking
|
|
8
|
+
- 👥 **User Management** - CRUD operations for user accounts
|
|
9
|
+
- ⚙️ **Configuration Management** - Easy setup and configuration
|
|
10
|
+
- 📊 **Multiple Output Formats** - JSON, table, and YAML output
|
|
11
|
+
- 🛡️ **Type Safety** - Built with TypeScript for reliability
|
|
12
|
+
- 🎨 **Rich CLI Experience** - Colorful output and interactive prompts
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
### Global Installation (Production)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g @burdenoff/bigconsole-cli
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Global Installation (Alpha/Pre-release)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g @burdenoff/alpha-bigconsole-cli
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Local Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @burdenoff/bigconsole-cli
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
Configure the API endpoint:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bigconsole config set apiEndpoint http://localhost:8000/graphql
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Authentication
|
|
43
|
+
|
|
44
|
+
### Login with credentials
|
|
45
|
+
```bash
|
|
46
|
+
bigconsole auth login
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Login with token
|
|
50
|
+
```bash
|
|
51
|
+
bigconsole auth login --token <your-access-token>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Check authentication status
|
|
55
|
+
```bash
|
|
56
|
+
bigconsole auth status
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Logout
|
|
60
|
+
```bash
|
|
61
|
+
bigconsole auth logout
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Change password
|
|
65
|
+
```bash
|
|
66
|
+
bigconsole auth change-password
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## User Management
|
|
70
|
+
|
|
71
|
+
### List users
|
|
72
|
+
```bash
|
|
73
|
+
bigconsole users list
|
|
74
|
+
bigconsole users list --page 2 --limit 20
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Get user details
|
|
78
|
+
```bash
|
|
79
|
+
bigconsole users get <user-id>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Create a user
|
|
83
|
+
```bash
|
|
84
|
+
bigconsole users create
|
|
85
|
+
bigconsole users create --email user@example.com --password password123 --fullName "John Doe"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Update a user
|
|
89
|
+
```bash
|
|
90
|
+
bigconsole users update <user-id> --email newemail@example.com
|
|
91
|
+
bigconsole users update <user-id> --active
|
|
92
|
+
bigconsole users update <user-id> --superuser
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Delete a user
|
|
96
|
+
```bash
|
|
97
|
+
bigconsole users delete <user-id>
|
|
98
|
+
bigconsole users delete <user-id> --force
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Configuration Management
|
|
102
|
+
|
|
103
|
+
### View all configuration
|
|
104
|
+
```bash
|
|
105
|
+
bigconsole config get
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Get specific configuration
|
|
109
|
+
```bash
|
|
110
|
+
bigconsole config get apiEndpoint
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Set configuration
|
|
114
|
+
```bash
|
|
115
|
+
bigconsole config set apiEndpoint http://localhost:8000/graphql
|
|
116
|
+
bigconsole config set outputFormat table
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Delete configuration
|
|
120
|
+
```bash
|
|
121
|
+
bigconsole config delete accessToken
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Clear all configuration
|
|
125
|
+
```bash
|
|
126
|
+
bigconsole config clear
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Show config file path
|
|
130
|
+
```bash
|
|
131
|
+
bigconsole config path
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Output Formats
|
|
135
|
+
|
|
136
|
+
All commands support different output formats:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
bigconsole users list --format json
|
|
140
|
+
bigconsole users list --format table
|
|
141
|
+
bigconsole users list --format yaml
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Global Options
|
|
145
|
+
|
|
146
|
+
- `--endpoint <endpoint>`: Override the API endpoint for a single command
|
|
147
|
+
- `--token <token>`: Override the access token for a single command
|
|
148
|
+
- `--format <format>`: Set output format (json, table, yaml)
|
|
149
|
+
- `--verbose`: Enable verbose logging
|
|
150
|
+
|
|
151
|
+
## Examples
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Set up the CLI
|
|
155
|
+
bigconsole config set apiEndpoint http://localhost:8000/graphql
|
|
156
|
+
|
|
157
|
+
# Login
|
|
158
|
+
bigconsole auth login
|
|
159
|
+
|
|
160
|
+
# List all users in table format
|
|
161
|
+
bigconsole users list --format table
|
|
162
|
+
|
|
163
|
+
# Create a new user
|
|
164
|
+
bigconsole users create --email admin@example.com --password admin123 --superuser
|
|
165
|
+
|
|
166
|
+
# Check authentication status
|
|
167
|
+
bigconsole auth status
|
|
168
|
+
|
|
169
|
+
# Get help for any command
|
|
170
|
+
bigconsole auth --help
|
|
171
|
+
bigconsole users --help
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Development
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Install dependencies
|
|
178
|
+
npm install
|
|
179
|
+
|
|
180
|
+
# Build the project
|
|
181
|
+
npm run build
|
|
182
|
+
|
|
183
|
+
# Run in development mode
|
|
184
|
+
npm run dev
|
|
185
|
+
|
|
186
|
+
# Run tests
|
|
187
|
+
npm test
|
|
188
|
+
|
|
189
|
+
# Lint code
|
|
190
|
+
npm run lint
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Configuration File
|
|
194
|
+
|
|
195
|
+
The CLI stores configuration in `~/.config/bigconsole-cli/config.json`. This includes:
|
|
196
|
+
|
|
197
|
+
- `apiEndpoint`: The GraphQL API endpoint
|
|
198
|
+
- `outputFormat`: Default output format (json, table, yaml)
|
|
199
|
+
- `accessToken`: Authentication token
|
|
200
|
+
- `refreshToken`: Refresh token for authentication
|
|
201
|
+
|
|
202
|
+
## CI/CD
|
|
203
|
+
|
|
204
|
+
The project includes automated CI/CD pipelines:
|
|
205
|
+
|
|
206
|
+
- **Alpha builds** - Published to `@burdenoff/alpha-bigconsole-cli` from `alpha` branch
|
|
207
|
+
- **Production builds** - Published to `@burdenoff/bigconsole-cli` from `main` branch
|
|
208
|
+
|
|
209
|
+
## Testing
|
|
210
|
+
|
|
211
|
+
See [TESTING.md](./TESTING.md) for comprehensive testing instructions before publishing.
|
|
212
|
+
|
|
213
|
+
## Security
|
|
214
|
+
|
|
215
|
+
- Tokens are stored securely in user configuration directory
|
|
216
|
+
- No sensitive information is logged in verbose mode
|
|
217
|
+
- All API communication uses HTTPS
|
|
218
|
+
- Input validation prevents injection attacks
|
|
219
|
+
|
|
220
|
+
## Contributing
|
|
221
|
+
|
|
222
|
+
1. Fork the repository
|
|
223
|
+
2. Create a feature branch
|
|
224
|
+
3. Make your changes
|
|
225
|
+
4. Add tests for new functionality
|
|
226
|
+
5. Ensure all tests pass
|
|
227
|
+
6. Submit a pull request
|
|
228
|
+
|
|
229
|
+
## License
|
|
230
|
+
|
|
231
|
+
UNLICENSED - Private package for internal use
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
Built with ❤️ by [Vignesh T.V](https://github.com/tvvignesh)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth2 Device Code flow implementation.
|
|
3
|
+
* Used for CLI authentication (similar to `gh auth login`).
|
|
4
|
+
*/
|
|
5
|
+
import type { CLIConfig } from '../config/index.js';
|
|
6
|
+
export interface DeviceCodeResponse {
|
|
7
|
+
deviceCode: string;
|
|
8
|
+
userCode: string;
|
|
9
|
+
verificationUri: string;
|
|
10
|
+
verificationUriComplete: string | null;
|
|
11
|
+
expiresIn: number;
|
|
12
|
+
interval: number;
|
|
13
|
+
}
|
|
14
|
+
export interface DeviceCodeTokenResult {
|
|
15
|
+
accessToken: string;
|
|
16
|
+
refreshToken: string;
|
|
17
|
+
expiresIn: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Start the device code flow by requesting a device code from the OIDC issuer.
|
|
21
|
+
*/
|
|
22
|
+
export declare function startDeviceCodeFlow(config: CLIConfig & {
|
|
23
|
+
oidcIssuer: string;
|
|
24
|
+
clientId: string;
|
|
25
|
+
}): Promise<DeviceCodeResponse>;
|
|
26
|
+
/**
|
|
27
|
+
* Poll the token endpoint until the user approves or the code expires.
|
|
28
|
+
*/
|
|
29
|
+
export declare function pollForToken(deviceCode: string, interval: number, config: CLIConfig & {
|
|
30
|
+
oidcIssuer: string;
|
|
31
|
+
clientId: string;
|
|
32
|
+
}): Promise<DeviceCodeTokenResult>;
|
|
33
|
+
export interface ClientCredentialsResult {
|
|
34
|
+
accessToken: string;
|
|
35
|
+
expiresIn: number;
|
|
36
|
+
scopes: string[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Authenticate using client credentials (app authentication).
|
|
40
|
+
* Calls the AuthenticateApp mutation on the global gateway.
|
|
41
|
+
*/
|
|
42
|
+
export declare function clientCredentialsFlow(globalEndpoint: string, clientId: string, clientSecret: string, scopes?: string[]): Promise<ClientCredentialsResult>;
|
|
43
|
+
/**
|
|
44
|
+
* Issue a workspace token by calling the workspace gateway with an auth token.
|
|
45
|
+
* Only sends Authorization header (no X-Workspace-Authorization).
|
|
46
|
+
*/
|
|
47
|
+
export declare function issueWorkspaceToken(workspaceEndpoint: string, authToken: string, workspaceId: string, organizationId: string): Promise<{
|
|
48
|
+
token: string;
|
|
49
|
+
expiresAt: string;
|
|
50
|
+
scopes: string[];
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* Fetch user's workspaces from the global gateway.
|
|
54
|
+
*/
|
|
55
|
+
export declare function fetchWorkspaces(globalEndpoint: string, authToken: string): Promise<Array<{
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
slug: string;
|
|
59
|
+
tenantId: string;
|
|
60
|
+
organizationId: string;
|
|
61
|
+
}>>;
|
|
62
|
+
/**
|
|
63
|
+
* Attempt to open a URL in the user's default browser.
|
|
64
|
+
* Uses execFile instead of exec to avoid shell interpretation and command injection.
|
|
65
|
+
*/
|
|
66
|
+
export declare function openBrowser(url: string): Promise<boolean>;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth2 Device Code flow implementation.
|
|
3
|
+
* Used for CLI authentication (similar to `gh auth login`).
|
|
4
|
+
*/
|
|
5
|
+
import fetch from 'cross-fetch';
|
|
6
|
+
/**
|
|
7
|
+
* Start the device code flow by requesting a device code from the OIDC issuer.
|
|
8
|
+
*/
|
|
9
|
+
export async function startDeviceCodeFlow(config) {
|
|
10
|
+
const url = `${config.oidcIssuer}/oidc/device/code`;
|
|
11
|
+
const body = new URLSearchParams({
|
|
12
|
+
client_id: config.clientId,
|
|
13
|
+
scope: 'openid profile email offline_access',
|
|
14
|
+
});
|
|
15
|
+
const response = await fetch(url, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
18
|
+
body: body.toString(),
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
const text = await response.text().catch(() => 'Unknown error');
|
|
22
|
+
throw new Error(`Device code request failed (${response.status}): ${text}`);
|
|
23
|
+
}
|
|
24
|
+
const data = (await response.json());
|
|
25
|
+
return {
|
|
26
|
+
deviceCode: data['device_code'],
|
|
27
|
+
userCode: data['user_code'],
|
|
28
|
+
verificationUri: data['verification_uri'],
|
|
29
|
+
verificationUriComplete: data['verification_uri_complete'] ?? null,
|
|
30
|
+
expiresIn: data['expires_in'],
|
|
31
|
+
interval: data['interval'] ?? 5,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Poll the token endpoint until the user approves or the code expires.
|
|
36
|
+
*/
|
|
37
|
+
export async function pollForToken(deviceCode, interval, config) {
|
|
38
|
+
const url = `${config.oidcIssuer}/oauth/device/token`;
|
|
39
|
+
let pollInterval = interval;
|
|
40
|
+
while (true) {
|
|
41
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval * 1000));
|
|
42
|
+
const response = await fetch(url, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
45
|
+
body: new URLSearchParams({
|
|
46
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
47
|
+
device_code: deviceCode,
|
|
48
|
+
client_id: config.clientId,
|
|
49
|
+
}).toString(),
|
|
50
|
+
});
|
|
51
|
+
// Guard against non-JSON responses (e.g. a 502 HTML error page from a
|
|
52
|
+
// gateway during a restart). Parsing those as JSON throws SyntaxError,
|
|
53
|
+
// aborts the whole poll loop, and forces the user to re-run
|
|
54
|
+
// `bigconsole auth login` from scratch even though the transient error
|
|
55
|
+
// would have cleared on the next tick.
|
|
56
|
+
const raw = await response.text();
|
|
57
|
+
let data;
|
|
58
|
+
try {
|
|
59
|
+
data = JSON.parse(raw);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
if (response.status >= 500) {
|
|
63
|
+
// Treat it like authorization_pending — keep polling.
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
throw new Error(`Token endpoint returned non-JSON response (status ${response.status}): ${raw.slice(0, 200)}`);
|
|
67
|
+
}
|
|
68
|
+
if (response.ok && data['access_token']) {
|
|
69
|
+
return {
|
|
70
|
+
accessToken: data['access_token'],
|
|
71
|
+
refreshToken: data['refresh_token'] ?? '',
|
|
72
|
+
expiresIn: data['expires_in'] ?? 3600,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const errorCode = data['error'];
|
|
76
|
+
switch (errorCode) {
|
|
77
|
+
case 'authorization_pending':
|
|
78
|
+
continue;
|
|
79
|
+
case 'slow_down':
|
|
80
|
+
pollInterval += 5;
|
|
81
|
+
continue;
|
|
82
|
+
case 'expired_token':
|
|
83
|
+
throw new Error('Device code has expired. Please try again.');
|
|
84
|
+
case 'access_denied':
|
|
85
|
+
throw new Error('Authorization was denied by the user.');
|
|
86
|
+
default:
|
|
87
|
+
throw new Error(`Device code polling failed: ${data['error_description'] ?? errorCode ?? 'Unknown error'}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Authenticate using client credentials (app authentication).
|
|
93
|
+
* Calls the AuthenticateApp mutation on the global gateway.
|
|
94
|
+
*/
|
|
95
|
+
export async function clientCredentialsFlow(globalEndpoint, clientId, clientSecret, scopes) {
|
|
96
|
+
const query = `
|
|
97
|
+
mutation AuthenticateApp($input: AuthenticateAppInput!) {
|
|
98
|
+
authenticateApp(input: $input) {
|
|
99
|
+
accessToken tokenType expiresIn scopes
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
`;
|
|
103
|
+
const response = await fetch(globalEndpoint, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: { 'Content-Type': 'application/json' },
|
|
106
|
+
body: JSON.stringify({
|
|
107
|
+
query,
|
|
108
|
+
variables: { input: { clientId, clientSecret, scopes } },
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
const text = await response.text().catch(() => 'Unknown error');
|
|
113
|
+
throw new Error(`App authentication failed (${response.status}): ${text}`);
|
|
114
|
+
}
|
|
115
|
+
const data = (await response.json());
|
|
116
|
+
if (data.errors?.length) {
|
|
117
|
+
throw new Error(`App authentication failed: ${data.errors[0].message}`);
|
|
118
|
+
}
|
|
119
|
+
if (!data.data?.authenticateApp?.accessToken) {
|
|
120
|
+
throw new Error('App authentication failed: no access token returned');
|
|
121
|
+
}
|
|
122
|
+
return data.data.authenticateApp;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Issue a workspace token by calling the workspace gateway with an auth token.
|
|
126
|
+
* Only sends Authorization header (no X-Workspace-Authorization).
|
|
127
|
+
*/
|
|
128
|
+
export async function issueWorkspaceToken(workspaceEndpoint, authToken, workspaceId, organizationId) {
|
|
129
|
+
const query = `
|
|
130
|
+
mutation IssueWorkspaceToken($input: IssueWorkspaceTokenInput!) {
|
|
131
|
+
issueWorkspaceToken(input: $input) {
|
|
132
|
+
success token expiresAt scopes
|
|
133
|
+
error { code message }
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
`;
|
|
137
|
+
const response = await fetch(workspaceEndpoint, {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: {
|
|
140
|
+
'Content-Type': 'application/json',
|
|
141
|
+
Authorization: `Bearer ${authToken}`,
|
|
142
|
+
},
|
|
143
|
+
body: JSON.stringify({
|
|
144
|
+
query,
|
|
145
|
+
variables: { input: { workspaceId, organizationId } },
|
|
146
|
+
}),
|
|
147
|
+
});
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
const text = await response.text().catch(() => 'Unknown error');
|
|
150
|
+
throw new Error(`Workspace token request failed (${response.status}): ${text}`);
|
|
151
|
+
}
|
|
152
|
+
const data = (await response.json());
|
|
153
|
+
if (data.errors?.length) {
|
|
154
|
+
throw new Error(`Workspace token failed: ${data.errors[0].message}`);
|
|
155
|
+
}
|
|
156
|
+
const result = data.data?.issueWorkspaceToken;
|
|
157
|
+
if (!result?.success || !result.token) {
|
|
158
|
+
const errMsg = result?.error?.message ?? 'Unknown error';
|
|
159
|
+
throw new Error(`Workspace token failed: ${errMsg}`);
|
|
160
|
+
}
|
|
161
|
+
return { token: result.token, expiresAt: result.expiresAt, scopes: result.scopes ?? [] };
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Fetch user's workspaces from the global gateway.
|
|
165
|
+
*/
|
|
166
|
+
export async function fetchWorkspaces(globalEndpoint, authToken) {
|
|
167
|
+
const query = `
|
|
168
|
+
query MyWorkspaces($limit: Int) {
|
|
169
|
+
myWorkspaces(limit: $limit) {
|
|
170
|
+
items { id name slug tenantId organizationId }
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
`;
|
|
174
|
+
const response = await fetch(globalEndpoint, {
|
|
175
|
+
method: 'POST',
|
|
176
|
+
headers: {
|
|
177
|
+
'Content-Type': 'application/json',
|
|
178
|
+
Authorization: `Bearer ${authToken}`,
|
|
179
|
+
},
|
|
180
|
+
body: JSON.stringify({ query, variables: { limit: 50 } }),
|
|
181
|
+
});
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
const text = await response.text().catch(() => 'Unknown error');
|
|
184
|
+
throw new Error(`Failed to fetch workspaces (${response.status}): ${text}`);
|
|
185
|
+
}
|
|
186
|
+
const data = (await response.json());
|
|
187
|
+
if (data.errors?.length) {
|
|
188
|
+
throw new Error(`Failed to fetch workspaces: ${data.errors[0].message}`);
|
|
189
|
+
}
|
|
190
|
+
return data.data?.myWorkspaces?.items ?? [];
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Validate that a string is a well-formed HTTP(S) URL.
|
|
194
|
+
* Prevents command injection by rejecting anything that isn't a valid URL.
|
|
195
|
+
*/
|
|
196
|
+
function isValidHttpUrl(urlString) {
|
|
197
|
+
try {
|
|
198
|
+
const parsed = new URL(urlString);
|
|
199
|
+
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Attempt to open a URL in the user's default browser.
|
|
207
|
+
* Uses execFile instead of exec to avoid shell interpretation and command injection.
|
|
208
|
+
*/
|
|
209
|
+
export async function openBrowser(url) {
|
|
210
|
+
try {
|
|
211
|
+
if (!isValidHttpUrl(url)) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
const { execFile } = await import('child_process');
|
|
215
|
+
const platform = process.platform;
|
|
216
|
+
let command;
|
|
217
|
+
let args;
|
|
218
|
+
if (platform === 'darwin') {
|
|
219
|
+
command = 'open';
|
|
220
|
+
args = [url];
|
|
221
|
+
}
|
|
222
|
+
else if (platform === 'win32') {
|
|
223
|
+
command = 'cmd';
|
|
224
|
+
args = ['/c', 'start', '', url];
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
command = 'xdg-open';
|
|
228
|
+
args = [url];
|
|
229
|
+
}
|
|
230
|
+
return new Promise((resolve) => {
|
|
231
|
+
execFile(command, args, (error) => {
|
|
232
|
+
resolve(!error);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth token persistence.
|
|
3
|
+
* Stores tokens in ~/.config/bigconsole-cli/auth.json with restrictive permissions.
|
|
4
|
+
*/
|
|
5
|
+
export interface StoredAuth {
|
|
6
|
+
accessToken: string;
|
|
7
|
+
refreshToken: string;
|
|
8
|
+
expiresAt?: number;
|
|
9
|
+
workspaceToken?: string;
|
|
10
|
+
workspaceTokenExpiresAt?: number;
|
|
11
|
+
workspaceId?: string;
|
|
12
|
+
organizationId?: string;
|
|
13
|
+
authMethod?: 'device_code' | 'client_credentials';
|
|
14
|
+
clientId?: string;
|
|
15
|
+
clientSecret?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Read stored authentication data.
|
|
19
|
+
* Returns null if no auth data exists.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readAuth(): StoredAuth | null;
|
|
22
|
+
/**
|
|
23
|
+
* Write authentication data to the store.
|
|
24
|
+
* File permissions are set to 0o600 (owner read/write only).
|
|
25
|
+
*/
|
|
26
|
+
export declare function writeAuth(auth: StoredAuth): void;
|
|
27
|
+
/**
|
|
28
|
+
* Clear stored authentication data.
|
|
29
|
+
*/
|
|
30
|
+
export declare function clearAuth(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Check if the stored token has expired.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isTokenExpired(auth: StoredAuth): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Check if the stored token is expiring within the next 5 minutes.
|
|
37
|
+
*/
|
|
38
|
+
export declare function isTokenExpiringSoon(auth: StoredAuth): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Check if the workspace token has expired.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isWorkspaceTokenExpired(auth: StoredAuth): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Check if the workspace token is expiring within the next 5 minutes.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isWorkspaceTokenExpiringSoon(auth: StoredAuth): boolean;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth token persistence.
|
|
3
|
+
* Stores tokens in ~/.config/bigconsole-cli/auth.json with restrictive permissions.
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, unlinkSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
const CONFIG_DIR = join(homedir(), '.config', 'bigconsole-cli');
|
|
9
|
+
const AUTH_FILE = join(CONFIG_DIR, 'auth.json');
|
|
10
|
+
function ensureConfigDir() {
|
|
11
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
12
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Read stored authentication data.
|
|
17
|
+
* Returns null if no auth data exists.
|
|
18
|
+
*/
|
|
19
|
+
export function readAuth() {
|
|
20
|
+
try {
|
|
21
|
+
if (!existsSync(AUTH_FILE))
|
|
22
|
+
return null;
|
|
23
|
+
const raw = readFileSync(AUTH_FILE, 'utf8');
|
|
24
|
+
return JSON.parse(raw);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Write authentication data to the store.
|
|
32
|
+
* File permissions are set to 0o600 (owner read/write only).
|
|
33
|
+
*/
|
|
34
|
+
export function writeAuth(auth) {
|
|
35
|
+
ensureConfigDir();
|
|
36
|
+
writeFileSync(AUTH_FILE, JSON.stringify(auth, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
37
|
+
try {
|
|
38
|
+
chmodSync(AUTH_FILE, 0o600);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// chmod may fail on some platforms (Windows), that's acceptable
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Clear stored authentication data.
|
|
46
|
+
*/
|
|
47
|
+
export function clearAuth() {
|
|
48
|
+
try {
|
|
49
|
+
if (existsSync(AUTH_FILE)) {
|
|
50
|
+
unlinkSync(AUTH_FILE);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Ignore errors on cleanup
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Check if the stored token has expired.
|
|
59
|
+
*/
|
|
60
|
+
export function isTokenExpired(auth) {
|
|
61
|
+
if (!auth.expiresAt)
|
|
62
|
+
return false;
|
|
63
|
+
return Date.now() >= auth.expiresAt;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if the stored token is expiring within the next 5 minutes.
|
|
67
|
+
*/
|
|
68
|
+
export function isTokenExpiringSoon(auth) {
|
|
69
|
+
if (!auth.expiresAt)
|
|
70
|
+
return false;
|
|
71
|
+
const BUFFER_MS = 5 * 60 * 1000;
|
|
72
|
+
return Date.now() + BUFFER_MS >= auth.expiresAt;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Check if the workspace token has expired.
|
|
76
|
+
*/
|
|
77
|
+
export function isWorkspaceTokenExpired(auth) {
|
|
78
|
+
if (!auth.workspaceTokenExpiresAt)
|
|
79
|
+
return false;
|
|
80
|
+
return Date.now() >= auth.workspaceTokenExpiresAt;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if the workspace token is expiring within the next 5 minutes.
|
|
84
|
+
*/
|
|
85
|
+
export function isWorkspaceTokenExpiringSoon(auth) {
|
|
86
|
+
if (!auth.workspaceTokenExpiresAt)
|
|
87
|
+
return false;
|
|
88
|
+
const BUFFER_MS = 5 * 60 * 1000;
|
|
89
|
+
return Date.now() + BUFFER_MS >= auth.workspaceTokenExpiresAt;
|
|
90
|
+
}
|