mbkbucket 3.8.2 ā 4.0.0
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/.env.example +15 -14
- package/README.md +561 -377
- package/index.js +32 -30
- package/lib/cli.js +154 -565
- package/lib/public/bucketadmin.css +2042 -1708
- package/lib/public/bucketadmin.js +1622 -1286
- package/lib/public/mbkbucket-helper-demo.html +4 -24
- package/lib/public/mbkbucket-helper.css +28 -34
- package/lib/public/mbkbucket-helper.js +4 -3
- package/lib/src/config/validation.js +86 -73
- package/lib/src/controllers/api.controller.js +459 -410
- package/lib/src/controllers/dashboard.controller.js +69 -49
- package/lib/src/controllers/info.controller.js +34 -3
- package/lib/src/controllers/view.controller.js +233 -231
- package/lib/src/core/storage/errors.js +62 -0
- package/lib/src/core/storage/gdrive-auth.js +193 -0
- package/lib/src/core/storage/gdrive-provider.js +656 -0
- package/lib/src/core/storage/index.js +14 -0
- package/lib/src/core/storage/models.js +80 -0
- package/lib/src/core/storage/path-strategy.js +65 -0
- package/lib/src/core/storage/s3-provider.js +491 -0
- package/lib/src/core/storage/storage-manager.js +203 -0
- package/lib/src/core/storage/storage-provider.js +217 -0
- package/lib/src/middleware/bucket-resolver.js +37 -37
- package/lib/src/routes/api.routes.js +5 -1
- package/lib/src/routes/index.js +60 -60
- package/lib/src/routes/view.routes.js +28 -25
- package/lib/src/services/s3.service.js +42 -594
- package/lib/src/services/storage.service.js +213 -0
- package/lib/src/utils/errors.js +56 -46
- package/package.json +11 -12
- package/views/bucket.hbs +47 -71
- package/views/bucketadmin_move_modal.hbs +35 -0
- package/views/bucketadmin_preview_modal.hbs +108 -40
- package/views/bucketadmincontent.hbs +193 -144
- package/views/mbkbucket_info.hbs +134 -232
- package/.mbkbucket.config.example.json +0 -14
- package/lib/src/cli/auth-helper.js +0 -141
package/views/mbkbucket_info.hbs
CHANGED
|
@@ -1,233 +1,135 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
* MBKBucket
|
|
3
|
-
* Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
|
|
4
|
-
* Licensed under the MIT License.
|
|
5
|
-
* Source: https://github.com/MIbnEKhalid/mbkbucket
|
|
6
|
-
-->
|
|
7
|
-
|
|
8
|
-
<!DOCTYPE html>
|
|
9
|
-
<html lang="en">
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
color: var(--success);
|
|
136
|
-
border: 1px solid var(--success);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
.version-outdated {
|
|
140
|
-
background: rgba(255, 118, 117, 0.2);
|
|
141
|
-
color: var(--danger);
|
|
142
|
-
border: 1px solid var(--danger);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.version-fetch-error {
|
|
146
|
-
background: rgba(255, 209, 102, 0.2);
|
|
147
|
-
color: var(--warning);
|
|
148
|
-
border: 1px solid var(--warning);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
@media (max-width: 768px) {
|
|
152
|
-
.info-box {
|
|
153
|
-
padding: 2rem;
|
|
154
|
-
max-width: 100%;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
.info-title {
|
|
158
|
-
font-size: 1.8rem;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
.info-row {
|
|
162
|
-
flex-direction: column;
|
|
163
|
-
align-items: flex-start;
|
|
164
|
-
gap: 0.5rem;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.info-label {
|
|
168
|
-
min-width: unset;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.info-section h2 {
|
|
172
|
-
font-size: 1.2rem;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
@media (max-width: 576px) {
|
|
177
|
-
.info-box {
|
|
178
|
-
padding: 1.5rem;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.info-section {
|
|
182
|
-
padding: 1rem;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
</style>
|
|
186
|
-
</head>
|
|
187
|
-
|
|
188
|
-
<body>
|
|
189
|
-
<header>
|
|
190
|
-
<div class="header-container">
|
|
191
|
-
<a class="logo">
|
|
192
|
-
<img src="/icon.svg" alt="Logo" class="logo-image" />
|
|
193
|
-
<span class="logo-text">BK <span>Tech</span></span>
|
|
194
|
-
<span class="logo-comp">{{#if APP_NAME}}{{APP_NAME}}{{else}}mbkbucket{{/if}}
|
|
195
|
-
</a>
|
|
196
|
-
|
|
197
|
-
{{> profilemenu}}
|
|
198
|
-
</div>
|
|
199
|
-
</header>
|
|
200
|
-
<section class="login-container">
|
|
201
|
-
<i class="fas fa-info-circle ai-element"></i>
|
|
202
|
-
<i class="fas fa-cogs ai-element"></i>
|
|
203
|
-
<i class="fas fa-server ai-element"></i>
|
|
204
|
-
<i class="fas fa-code-branch ai-element"></i>
|
|
205
|
-
|
|
206
|
-
<div class="info-box">
|
|
207
|
-
<h1 class="info-title">System Information</h1>
|
|
208
|
-
|
|
209
|
-
<div class="info-section">
|
|
210
|
-
<h2><i class="fas fa-code-branch"></i> Version Information</h2>
|
|
211
|
-
<div class="info-row">
|
|
212
|
-
<div class="info-label">Current Version:</div>
|
|
213
|
-
<div class="info-value" id="CurrentVersion">{{CurrentVersion}}</div>
|
|
214
|
-
</div>
|
|
215
|
-
|
|
216
|
-
<div class="info-row">
|
|
217
|
-
<div class="info-label">Latest Version:</div>
|
|
218
|
-
<div class="info-value">
|
|
219
|
-
{{latestVersion}}
|
|
220
|
-
{{#if (eq latestVersion CurrentVersion)}}
|
|
221
|
-
<span class="version-status version-up-to-date">
|
|
222
|
-
<i class="fas fa-check-circle"></i> Up to date
|
|
223
|
-
</span>
|
|
224
|
-
{{/if}}
|
|
225
|
-
</div>
|
|
226
|
-
</div>
|
|
227
|
-
</div>
|
|
228
|
-
</div>
|
|
229
|
-
</section>
|
|
230
|
-
|
|
231
|
-
</body>
|
|
232
|
-
|
|
1
|
+
<!--
|
|
2
|
+
* MBKBucket
|
|
3
|
+
* Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
* Source: https://github.com/MIbnEKhalid/mbkbucket
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
<!DOCTYPE html>
|
|
9
|
+
<html lang="en">
|
|
10
|
+
|
|
11
|
+
{{> head pageTitle="System Information" ogUrl="/mbkbucket/info"}}
|
|
12
|
+
|
|
13
|
+
<body>
|
|
14
|
+
{{> header appName=APP_NAME customURL="/mbkbucket"}}
|
|
15
|
+
|
|
16
|
+
<section class="login-container">
|
|
17
|
+
{{> backgroundElements}}
|
|
18
|
+
|
|
19
|
+
<div class="auth-card info-box">
|
|
20
|
+
<div class="info-header">
|
|
21
|
+
<div class="auth-icon-badge" aria-hidden="true">
|
|
22
|
+
<i class="fas fa-database"></i>
|
|
23
|
+
</div>
|
|
24
|
+
<div>
|
|
25
|
+
<h1 class="login-title">Storage Diagnostic</h1>
|
|
26
|
+
<p class="login-subtitle">Runtime storage parameters, active bucket configurations, and version status.</p>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<!-- Quick Metrics Grid -->
|
|
31
|
+
<div class="info-stats-grid">
|
|
32
|
+
<div class="stat-card">
|
|
33
|
+
<div class="stat-card-header">
|
|
34
|
+
<span class="stat-label">MBKBucket Core</span>
|
|
35
|
+
<div class="stat-icon-wrap"><i class="fas fa-code-branch"></i></div>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="stat-value">v<span id="CurrentVersion">{{CurrentVersion}}</span></div>
|
|
38
|
+
<div class="stat-badge-row">
|
|
39
|
+
{{#if latestVersion}}
|
|
40
|
+
{{#if (eq latestVersion CurrentVersion)}}
|
|
41
|
+
<span class="badge badge-success"><i class="fas fa-check-circle"></i> Up to date</span>
|
|
42
|
+
{{else}}
|
|
43
|
+
<span class="badge badge-warning"><i class="fas fa-arrow-up"></i> Update: v{{latestVersion}}</span>
|
|
44
|
+
{{/if}}
|
|
45
|
+
{{else}}
|
|
46
|
+
<span class="badge badge-neutral">v{{CurrentVersion}}</span>
|
|
47
|
+
{{/if}}
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="stat-card">
|
|
52
|
+
<div class="stat-card-header">
|
|
53
|
+
<span class="stat-label">Host Application</span>
|
|
54
|
+
<div class="stat-icon-wrap"><i class="fas fa-cubes"></i></div>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="stat-value">{{#if APP_NAME}}{{APP_NAME}}{{else}}mbkbucket{{/if}}</div>
|
|
57
|
+
<div class="stat-sub-text">Release <code>v{{#if APP_VERSION}}{{APP_VERSION}}{{else}}{{CurrentVersion}}{{/if}}</code></div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div class="stat-card">
|
|
61
|
+
<div class="stat-card-header">
|
|
62
|
+
<span class="stat-label">Storage Engine</span>
|
|
63
|
+
<div class="stat-icon-wrap"><i class="fas fa-server"></i></div>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="stat-value">
|
|
66
|
+
<span class="badge badge-success"><i class="fas fa-circle-dot"></i> {{#if providerType}}{{providerType}}{{else}}S3/R2{{/if}}</span>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="stat-sub-text">{{#if defaultBucket}}Bucket: <code>{{defaultBucket}}</code>{{else}}Active Storage Engine{{/if}}</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<!-- Configuration Parameters -->
|
|
73
|
+
<div class="info-section">
|
|
74
|
+
<div class="info-section-header">
|
|
75
|
+
<h2><i class="fas fa-sliders"></i> Configuration Manifest</h2>
|
|
76
|
+
<span class="badge badge-neutral">Read-only</span>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="info-table-wrap">
|
|
80
|
+
<table class="info-table">
|
|
81
|
+
<tbody>
|
|
82
|
+
<tr>
|
|
83
|
+
<td class="prop-label"><i class="fas fa-tag"></i> APP_NAME</td>
|
|
84
|
+
<td class="prop-val"><code>{{#if APP_NAME}}{{APP_NAME}}{{else}}mbkbucket{{/if}}</code></td>
|
|
85
|
+
</tr>
|
|
86
|
+
<tr>
|
|
87
|
+
<td class="prop-label"><i class="fas fa-code-commit"></i> Current Version</td>
|
|
88
|
+
<td class="prop-val"><code>{{CurrentVersion}}</code></td>
|
|
89
|
+
</tr>
|
|
90
|
+
<tr>
|
|
91
|
+
<td class="prop-label"><i class="fas fa-database"></i> Default Bucket</td>
|
|
92
|
+
<td class="prop-val"><code>{{#if defaultBucket}}{{defaultBucket}}{{else}}default{{/if}}</code></td>
|
|
93
|
+
</tr>
|
|
94
|
+
<tr>
|
|
95
|
+
<td class="prop-label"><i class="fas fa-layer-group"></i> Configured Buckets</td>
|
|
96
|
+
<td class="prop-val"><span class="badge badge-neutral">{{#if configuredBucketsCount}}{{configuredBucketsCount}}{{else}}1{{/if}} bucket(s)</span></td>
|
|
97
|
+
</tr>
|
|
98
|
+
<tr>
|
|
99
|
+
<td class="prop-label"><i class="fas fa-eye"></i> Public View Enabled</td>
|
|
100
|
+
<td class="prop-val">
|
|
101
|
+
{{#if (eq mbkbucketVar.publiView_enabled "true")}}
|
|
102
|
+
<span class="badge badge-success">true</span>
|
|
103
|
+
{{else}}
|
|
104
|
+
<span class="badge badge-neutral">{{#if mbkbucketVar.publiView_enabled}}{{mbkbucketVar.publiView_enabled}}{{else}}false{{/if}}</span>
|
|
105
|
+
{{/if}}
|
|
106
|
+
</td>
|
|
107
|
+
</tr>
|
|
108
|
+
<tr>
|
|
109
|
+
<td class="prop-label"><i class="fas fa-file-invoice"></i> Inline View</td>
|
|
110
|
+
<td class="prop-val">
|
|
111
|
+
{{#if (eq mbkbucketVar.p_view_inline "true")}}
|
|
112
|
+
<span class="badge badge-success">true</span>
|
|
113
|
+
{{else}}
|
|
114
|
+
<span class="badge badge-neutral">{{#if mbkbucketVar.p_view_inline}}{{mbkbucketVar.p_view_inline}}{{else}}false{{/if}}</span>
|
|
115
|
+
{{/if}}
|
|
116
|
+
</td>
|
|
117
|
+
</tr>
|
|
118
|
+
</tbody>
|
|
119
|
+
</table>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<!-- Navigation Links -->
|
|
124
|
+
<div class="info-action-grid">
|
|
125
|
+
<a class="btn-outline" href="/mbkbucket"><i class="fas fa-folder-open"></i> File Dashboard</a>
|
|
126
|
+
<a class="btn-outline" href="/mbkbucket/helper-demo"><i class="fas fa-vial"></i> Helper Demo</a>
|
|
127
|
+
<a class="btn-outline" href="/mbkbucket/i.json"><i class="fas fa-code"></i> JSON Diagnostics</a>
|
|
128
|
+
<a class="btn-outline" href="https://github.com/MIbnEKhalid/mbkbucket" target="_blank" rel="noopener noreferrer"><i class="fab fa-github"></i> Repository</a>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</section>
|
|
132
|
+
|
|
133
|
+
{{> versionInfo}}
|
|
134
|
+
</body>
|
|
233
135
|
</html>
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"_schema": "mbkbucket CLI config ā stored at ~/.mbkbucket/config.json",
|
|
3
|
-
|
|
4
|
-
"token": "mbk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
5
|
-
"tokenPrefix": "mbk_xxxx",
|
|
6
|
-
"username": "your-username",
|
|
7
|
-
"serverUrl": "https://your-app.example.com",
|
|
8
|
-
"profile": {
|
|
9
|
-
"name": "Read-Write CLI",
|
|
10
|
-
"scope": "read-write",
|
|
11
|
-
"allowedApps": ["mbkbucket"]
|
|
12
|
-
},
|
|
13
|
-
"loggedInAt": "2026-01-01T00:00:00.000Z"
|
|
14
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI Authentication Helper
|
|
3
|
-
* Handles mbkauthe device-flow login for the mbkbucket CLI.
|
|
4
|
-
* Stores credentials in ~/.mbkbucket/config.json
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import fs from 'node:fs';
|
|
8
|
-
import path from 'node:path';
|
|
9
|
-
import os from 'node:os';
|
|
10
|
-
import { createLogger } from '#logger';
|
|
11
|
-
|
|
12
|
-
const debugCli = createLogger('cli:auth');
|
|
13
|
-
|
|
14
|
-
export const CONFIG_DIR = path.join(os.homedir(), '.mbkbucket');
|
|
15
|
-
export const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
16
|
-
const MAX_POLL_TIME_MS = 15 * 60 * 1000; // 15 minutes
|
|
17
|
-
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
18
|
-
|
|
19
|
-
export function readConfig() {
|
|
20
|
-
try {
|
|
21
|
-
return fs.existsSync(CONFIG_FILE) ? JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8')) : {};
|
|
22
|
-
} catch (err) {
|
|
23
|
-
debugCli('Failed to read config: %s', err.message);
|
|
24
|
-
return {};
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function writeConfig(config) {
|
|
29
|
-
try {
|
|
30
|
-
if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
31
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
32
|
-
} catch (err) {
|
|
33
|
-
console.error(`ā Failed to write config: ${err.message}`);
|
|
34
|
-
throw err;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function clearConfig() {
|
|
39
|
-
try {
|
|
40
|
-
if (fs.existsSync(CONFIG_FILE)) fs.unlinkSync(CONFIG_FILE);
|
|
41
|
-
return true;
|
|
42
|
-
} catch (err) {
|
|
43
|
-
debugCli('Failed to clear config: %s', err.message);
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function updateConfig(partial) {
|
|
49
|
-
const updated = { ...readConfig(), ...partial };
|
|
50
|
-
writeConfig(updated);
|
|
51
|
-
return updated;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function isLoggedIn() {
|
|
55
|
-
const { token, server_url } = readConfig();
|
|
56
|
-
return Boolean(token && server_url);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export const getStoredToken = () => readConfig().token || null;
|
|
60
|
-
export const getStoredServerUrl = () => readConfig().server_url || null;
|
|
61
|
-
|
|
62
|
-
export async function deviceFlowLogin({ server_url, profile_key }) {
|
|
63
|
-
const baseUrl = String(server_url).replace(/\/+$/, '');
|
|
64
|
-
console.log(`š Requesting device login from ${baseUrl} ...`);
|
|
65
|
-
|
|
66
|
-
let deviceResp;
|
|
67
|
-
try {
|
|
68
|
-
const res = await fetch(`${baseUrl}/api/cli/device`, {
|
|
69
|
-
method: 'POST',
|
|
70
|
-
headers: { 'Content-Type': 'application/json' },
|
|
71
|
-
body: JSON.stringify({
|
|
72
|
-
client_name: `mbkbucket-cli-${os.hostname()}`,
|
|
73
|
-
...(profile_key && { profile_key }),
|
|
74
|
-
}),
|
|
75
|
-
});
|
|
76
|
-
deviceResp = await res.json();
|
|
77
|
-
if (!deviceResp.success) throw new Error(deviceResp.message || 'Failed to initiate device login');
|
|
78
|
-
} catch (err) {
|
|
79
|
-
if (err.message.includes('Failed to initiate') || err.message.includes('fetch')) {
|
|
80
|
-
throw new Error(`Could not reach mbkauthe server at ${baseUrl}/api/cli/device.\nVerify the server URL and that CLI_AUTH_ENABLED=true on the server.`);
|
|
81
|
-
}
|
|
82
|
-
throw err;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const { verification_url = '', user_code = '', device_code, expires_in = 900, interval = 5, profile } = deviceResp;
|
|
86
|
-
|
|
87
|
-
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
88
|
-
console.log('ā ā');
|
|
89
|
-
console.log('ā š Open this URL in your browser to approve: ā');
|
|
90
|
-
console.log(`ā ${verification_url.padEnd(53)}ā`);
|
|
91
|
-
console.log('ā ā');
|
|
92
|
-
console.log(`ā š¢ Or enter this code: ${user_code.padEnd(30)}ā`);
|
|
93
|
-
console.log('ā ā');
|
|
94
|
-
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
95
|
-
|
|
96
|
-
if (profile) console.log(` Profile: ${profile.name} (scope: ${profile.scope || 'full'})`);
|
|
97
|
-
console.log(` Waiting for approval (expires in ${Math.floor(expires_in / 60)} minutes)...\n`);
|
|
98
|
-
|
|
99
|
-
const pollInterval = interval * 1000;
|
|
100
|
-
const deadline = Date.now() + MAX_POLL_TIME_MS;
|
|
101
|
-
let attempts = 0;
|
|
102
|
-
|
|
103
|
-
while (Date.now() < deadline) {
|
|
104
|
-
await sleep(pollInterval);
|
|
105
|
-
attempts++;
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
const pollRes = await fetch(`${baseUrl}/api/cli/device/token`, {
|
|
109
|
-
method: 'POST',
|
|
110
|
-
headers: { 'Content-Type': 'application/json' },
|
|
111
|
-
body: JSON.stringify({ device_code }),
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
const pollBody = await pollRes.json();
|
|
115
|
-
|
|
116
|
-
if (pollBody.status === 'approved' && pollBody.success) {
|
|
117
|
-
const token_prefix = pollBody.token_prefix || '';
|
|
118
|
-
console.log(`ā
Login approved! Welcome, ${pollBody.username}.`);
|
|
119
|
-
console.log(` Token prefix: ${token_prefix}...`);
|
|
120
|
-
return {
|
|
121
|
-
token: pollBody.token,
|
|
122
|
-
token_prefix,
|
|
123
|
-
username: pollBody.username,
|
|
124
|
-
profile: profile || null,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (pollBody.status === 'denied') throw new Error('Login request was denied.');
|
|
129
|
-
if (pollBody.status === 'expired') throw new Error('Login request expired. Please try again.');
|
|
130
|
-
if (pollBody.status === 'completed') throw new Error('This login session has already been completed.');
|
|
131
|
-
|
|
132
|
-
if (attempts % 5 === 0) process.stdout.write('.');
|
|
133
|
-
} catch (err) {
|
|
134
|
-
if (['denied', 'expired', 'completed'].some(k => err.message.includes(k))) throw err;
|
|
135
|
-
debugCli('Poll error (will retry): %s', err.message);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
throw new Error('Login timed out. Please try again.');
|
|
140
|
-
}
|
|
141
|
-
|