ds-01 1.0.5 → 1.0.6
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/.turbo/turbo-build.log +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +69 -18
- package/package.json +1 -1
- package/src/commands/login.ts +245 -65
package/.turbo/turbo-build.log
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwBpC,eAAO,MAAM,KAAK,SAuTd,CAAC"}
|
package/dist/commands/login.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { intro, outro, spinner, note, cancel } from "@clack/prompts";
|
|
2
|
+
import { intro, outro, spinner, note, cancel, } from "@clack/prompts";
|
|
3
3
|
import pc from "picocolors";
|
|
4
4
|
import open from "open";
|
|
5
5
|
import machineIdPkg from "node-machine-id";
|
|
6
6
|
import { saveToken } from "../utils/config.js";
|
|
7
7
|
import { createDeviceKey, getDevicePublicKey, } from "../utils/secureKeyStore.js";
|
|
8
|
-
const API_BASE_URL = process.env.DS01_API_URL ||
|
|
8
|
+
const API_BASE_URL = process.env.DS01_API_URL ||
|
|
9
|
+
"https://ds-01.vercel.app";
|
|
9
10
|
const { machineIdSync } = machineIdPkg;
|
|
10
11
|
export const login = new Command()
|
|
11
12
|
.name("login")
|
|
@@ -20,10 +21,15 @@ export const login = new Command()
|
|
|
20
21
|
// --------------------------------------------------
|
|
21
22
|
s.start("Preparing secure device identity...");
|
|
22
23
|
await createDeviceKey();
|
|
23
|
-
// Only the PUBLIC certificate
|
|
24
|
+
// Only the PUBLIC key/certificate leaves
|
|
25
|
+
// the user's machine.
|
|
24
26
|
const publicKey = await getDevicePublicKey();
|
|
25
27
|
// Existing machine fingerprint.
|
|
26
28
|
const hardwareId = machineIdSync();
|
|
29
|
+
// Detect the cryptographic key type.
|
|
30
|
+
const publicKeyType = process.platform === "win32"
|
|
31
|
+
? "windows-rsa"
|
|
32
|
+
: "macos-ec";
|
|
27
33
|
// --------------------------------------------------
|
|
28
34
|
// 2. Start device authorization
|
|
29
35
|
// --------------------------------------------------
|
|
@@ -36,7 +42,7 @@ export const login = new Command()
|
|
|
36
42
|
body: JSON.stringify({
|
|
37
43
|
machineId: hardwareId,
|
|
38
44
|
publicKey,
|
|
39
|
-
publicKeyType
|
|
45
|
+
publicKeyType,
|
|
40
46
|
}),
|
|
41
47
|
});
|
|
42
48
|
if (!initResponse.ok) {
|
|
@@ -44,8 +50,10 @@ export const login = new Command()
|
|
|
44
50
|
cancel(`Server responded with HTTP ${initResponse.status}`);
|
|
45
51
|
process.exit(1);
|
|
46
52
|
}
|
|
47
|
-
const { deviceCode, userCode, verificationUrl, interval } = await initResponse.json();
|
|
48
|
-
if (!deviceCode ||
|
|
53
|
+
const { deviceCode, userCode, verificationUrl, interval, } = await initResponse.json();
|
|
54
|
+
if (!deviceCode ||
|
|
55
|
+
!userCode ||
|
|
56
|
+
!verificationUrl) {
|
|
49
57
|
throw new Error("Authorization server returned an invalid device response.");
|
|
50
58
|
}
|
|
51
59
|
s.stop(pc.green("Secure device identity registered."));
|
|
@@ -58,15 +66,16 @@ export const login = new Command()
|
|
|
58
66
|
await open(verificationUrl);
|
|
59
67
|
}
|
|
60
68
|
catch {
|
|
61
|
-
// Browser launch failure
|
|
69
|
+
// Browser launch failure
|
|
70
|
+
// is non-fatal.
|
|
62
71
|
}
|
|
63
72
|
// --------------------------------------------------
|
|
64
73
|
// 4. Poll for authorization
|
|
65
74
|
// --------------------------------------------------
|
|
66
75
|
s.start("Waiting for web authorization...");
|
|
67
|
-
let token = null;
|
|
68
76
|
const pollInterval = (interval || 5) * 1000;
|
|
69
|
-
|
|
77
|
+
let token = null;
|
|
78
|
+
while (true) {
|
|
70
79
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
71
80
|
const tokenResponse = await fetch(`${API_BASE_URL}/api/auth/device/token`, {
|
|
72
81
|
method: "POST",
|
|
@@ -78,27 +87,69 @@ export const login = new Command()
|
|
|
78
87
|
machineId: hardwareId,
|
|
79
88
|
}),
|
|
80
89
|
});
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
90
|
+
let tokenData;
|
|
91
|
+
try {
|
|
92
|
+
tokenData =
|
|
93
|
+
await tokenResponse.json();
|
|
84
94
|
}
|
|
85
|
-
|
|
86
|
-
s.stop(pc.red("Authorization
|
|
87
|
-
cancel(
|
|
95
|
+
catch {
|
|
96
|
+
s.stop(pc.red("Authorization server returned invalid JSON."));
|
|
97
|
+
cancel("Unable to read authorization response.");
|
|
88
98
|
process.exit(1);
|
|
89
99
|
}
|
|
100
|
+
// --------------------------------------------------
|
|
101
|
+
// SUCCESS
|
|
102
|
+
// --------------------------------------------------
|
|
103
|
+
if (tokenResponse.ok &&
|
|
104
|
+
typeof tokenData.accessToken ===
|
|
105
|
+
"string" &&
|
|
106
|
+
tokenData.accessToken.length > 0) {
|
|
107
|
+
token =
|
|
108
|
+
tokenData.accessToken;
|
|
109
|
+
// IMPORTANT:
|
|
110
|
+
// Stop the spinner immediately.
|
|
111
|
+
s.stop(pc.green("Hardware signature linked & token verified!"));
|
|
112
|
+
// IMPORTANT:
|
|
113
|
+
// Leave the polling loop immediately.
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
// --------------------------------------------------
|
|
117
|
+
// STILL WAITING
|
|
118
|
+
// --------------------------------------------------
|
|
119
|
+
if (tokenData.error ===
|
|
120
|
+
"authorization_pending") {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
// --------------------------------------------------
|
|
124
|
+
// FAILED / EXPIRED
|
|
125
|
+
// --------------------------------------------------
|
|
126
|
+
s.stop(pc.red("Authorization failed or expired."));
|
|
127
|
+
cancel(`Reason: ${tokenData.error ||
|
|
128
|
+
"Unknown authorization error"}`);
|
|
129
|
+
process.exit(1);
|
|
90
130
|
}
|
|
91
131
|
// --------------------------------------------------
|
|
92
|
-
// 5.
|
|
132
|
+
// 5. Make absolutely sure token exists
|
|
133
|
+
// --------------------------------------------------
|
|
134
|
+
if (!token) {
|
|
135
|
+
throw new Error("Authorization completed without receiving an access token.");
|
|
136
|
+
}
|
|
137
|
+
// --------------------------------------------------
|
|
138
|
+
// 6. Save CLI token + machine ID
|
|
93
139
|
// --------------------------------------------------
|
|
94
140
|
saveToken(token, hardwareId);
|
|
95
|
-
|
|
141
|
+
// --------------------------------------------------
|
|
142
|
+
// 7. Success
|
|
143
|
+
// --------------------------------------------------
|
|
96
144
|
outro(`${pc.white("✔")} ${pc.bold("Terminal synced successfully")}\n` +
|
|
97
145
|
pc.gray("Secure device key and session token are ready."));
|
|
98
146
|
}
|
|
99
147
|
catch (error) {
|
|
148
|
+
// Only stop the spinner if it is
|
|
149
|
+
// still running.
|
|
100
150
|
s.stop(pc.red("An error occurred during login."));
|
|
101
|
-
cancel(pc.gray(error?.message ||
|
|
151
|
+
cancel(pc.gray(error?.message ||
|
|
152
|
+
"Unknown CLI Error"));
|
|
102
153
|
process.exit(1);
|
|
103
154
|
}
|
|
104
155
|
});
|
package/package.json
CHANGED
package/src/commands/login.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
intro,
|
|
4
|
+
outro,
|
|
5
|
+
spinner,
|
|
6
|
+
note,
|
|
7
|
+
cancel,
|
|
8
|
+
} from "@clack/prompts";
|
|
3
9
|
import pc from "picocolors";
|
|
4
10
|
import open from "open";
|
|
5
11
|
import machineIdPkg from "node-machine-id";
|
|
@@ -10,18 +16,26 @@ import {
|
|
|
10
16
|
getDevicePublicKey,
|
|
11
17
|
} from "../utils/secureKeyStore.js";
|
|
12
18
|
|
|
13
|
-
const API_BASE_URL =
|
|
19
|
+
const API_BASE_URL =
|
|
20
|
+
process.env.DS01_API_URL ||
|
|
21
|
+
"https://ds-01.vercel.app";
|
|
14
22
|
|
|
15
23
|
const { machineIdSync } = machineIdPkg;
|
|
16
24
|
|
|
17
25
|
export const login = new Command()
|
|
18
26
|
.name("login")
|
|
19
|
-
.description(
|
|
27
|
+
.description(
|
|
28
|
+
"Authenticate your terminal with DS01 via browser",
|
|
29
|
+
)
|
|
20
30
|
.action(async () => {
|
|
21
31
|
console.log();
|
|
22
32
|
|
|
23
33
|
intro(
|
|
24
|
-
`${pc.bgWhite(
|
|
34
|
+
`${pc.bgWhite(
|
|
35
|
+
pc.black(
|
|
36
|
+
pc.bold(" DS01 "),
|
|
37
|
+
),
|
|
38
|
+
)} ${pc.gray(
|
|
25
39
|
"v1.0.0 — Terminal Authentication",
|
|
26
40
|
)}`,
|
|
27
41
|
);
|
|
@@ -32,125 +46,291 @@ export const login = new Command()
|
|
|
32
46
|
// --------------------------------------------------
|
|
33
47
|
// 1. Create / load the device's secure key
|
|
34
48
|
// --------------------------------------------------
|
|
35
|
-
|
|
49
|
+
|
|
50
|
+
s.start(
|
|
51
|
+
"Preparing secure device identity...",
|
|
52
|
+
);
|
|
36
53
|
|
|
37
54
|
await createDeviceKey();
|
|
38
55
|
|
|
39
|
-
// Only the PUBLIC certificate
|
|
40
|
-
|
|
56
|
+
// Only the PUBLIC key/certificate leaves
|
|
57
|
+
// the user's machine.
|
|
58
|
+
const publicKey =
|
|
59
|
+
await getDevicePublicKey();
|
|
41
60
|
|
|
42
61
|
// Existing machine fingerprint.
|
|
43
|
-
const hardwareId =
|
|
62
|
+
const hardwareId =
|
|
63
|
+
machineIdSync();
|
|
64
|
+
|
|
65
|
+
// Detect the cryptographic key type.
|
|
66
|
+
const publicKeyType =
|
|
67
|
+
process.platform === "win32"
|
|
68
|
+
? "windows-rsa"
|
|
69
|
+
: "macos-ec";
|
|
44
70
|
|
|
45
71
|
// --------------------------------------------------
|
|
46
72
|
// 2. Start device authorization
|
|
47
73
|
// --------------------------------------------------
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
74
|
+
|
|
75
|
+
s.start(
|
|
76
|
+
"Requesting pairing code...",
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const initResponse =
|
|
80
|
+
await fetch(
|
|
81
|
+
`${API_BASE_URL}/api/auth/device/code`,
|
|
82
|
+
{
|
|
83
|
+
method: "POST",
|
|
84
|
+
|
|
85
|
+
headers: {
|
|
86
|
+
"Content-Type":
|
|
87
|
+
"application/json",
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
machineId:
|
|
92
|
+
hardwareId,
|
|
93
|
+
|
|
94
|
+
publicKey,
|
|
95
|
+
|
|
96
|
+
publicKeyType,
|
|
97
|
+
}),
|
|
98
|
+
},
|
|
99
|
+
);
|
|
62
100
|
|
|
63
101
|
if (!initResponse.ok) {
|
|
64
|
-
s.stop(
|
|
102
|
+
s.stop(
|
|
103
|
+
pc.red(
|
|
104
|
+
"Failed to reach DS01 authorization server.",
|
|
105
|
+
),
|
|
106
|
+
);
|
|
65
107
|
|
|
66
|
-
cancel(
|
|
108
|
+
cancel(
|
|
109
|
+
`Server responded with HTTP ${initResponse.status}`,
|
|
110
|
+
);
|
|
67
111
|
|
|
68
112
|
process.exit(1);
|
|
69
113
|
}
|
|
70
114
|
|
|
71
|
-
const {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
115
|
+
const {
|
|
116
|
+
deviceCode,
|
|
117
|
+
userCode,
|
|
118
|
+
verificationUrl,
|
|
119
|
+
interval,
|
|
120
|
+
} = await initResponse.json();
|
|
121
|
+
|
|
122
|
+
if (
|
|
123
|
+
!deviceCode ||
|
|
124
|
+
!userCode ||
|
|
125
|
+
!verificationUrl
|
|
126
|
+
) {
|
|
75
127
|
throw new Error(
|
|
76
128
|
"Authorization server returned an invalid device response.",
|
|
77
129
|
);
|
|
78
130
|
}
|
|
79
131
|
|
|
80
|
-
s.stop(
|
|
132
|
+
s.stop(
|
|
133
|
+
pc.green(
|
|
134
|
+
"Secure device identity registered.",
|
|
135
|
+
),
|
|
136
|
+
);
|
|
81
137
|
|
|
82
138
|
// --------------------------------------------------
|
|
83
139
|
// 3. Ask user to approve in browser
|
|
84
140
|
// --------------------------------------------------
|
|
141
|
+
|
|
85
142
|
note(
|
|
86
|
-
`${pc.bold(
|
|
87
|
-
|
|
143
|
+
`${pc.bold(
|
|
144
|
+
"Pairing Code:",
|
|
145
|
+
)} ${pc.cyan(
|
|
146
|
+
pc.bold(
|
|
147
|
+
` ${userCode} `,
|
|
148
|
+
),
|
|
88
149
|
)}\n` +
|
|
89
|
-
`${pc.bold(
|
|
150
|
+
`${pc.bold(
|
|
151
|
+
"Verification URL:",
|
|
152
|
+
)} ${pc.underline(
|
|
153
|
+
verificationUrl,
|
|
154
|
+
)}`,
|
|
90
155
|
"Action Required",
|
|
91
156
|
);
|
|
92
157
|
|
|
93
158
|
try {
|
|
94
|
-
await open(
|
|
159
|
+
await open(
|
|
160
|
+
verificationUrl,
|
|
161
|
+
);
|
|
95
162
|
} catch {
|
|
96
|
-
// Browser launch failure
|
|
163
|
+
// Browser launch failure
|
|
164
|
+
// is non-fatal.
|
|
97
165
|
}
|
|
98
166
|
|
|
99
167
|
// --------------------------------------------------
|
|
100
168
|
// 4. Poll for authorization
|
|
101
169
|
// --------------------------------------------------
|
|
102
|
-
s.start("Waiting for web authorization...");
|
|
103
170
|
|
|
104
|
-
|
|
171
|
+
s.start(
|
|
172
|
+
"Waiting for web authorization...",
|
|
173
|
+
);
|
|
105
174
|
|
|
106
|
-
const pollInterval =
|
|
175
|
+
const pollInterval =
|
|
176
|
+
(interval || 5) * 1000;
|
|
107
177
|
|
|
108
|
-
|
|
109
|
-
|
|
178
|
+
let token: string | null =
|
|
179
|
+
null;
|
|
110
180
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
body: JSON.stringify({
|
|
119
|
-
deviceCode,
|
|
120
|
-
machineId: hardwareId,
|
|
121
|
-
}),
|
|
122
|
-
},
|
|
181
|
+
while (true) {
|
|
182
|
+
await new Promise(
|
|
183
|
+
(resolve) =>
|
|
184
|
+
setTimeout(
|
|
185
|
+
resolve,
|
|
186
|
+
pollInterval,
|
|
187
|
+
),
|
|
123
188
|
);
|
|
124
189
|
|
|
125
|
-
const
|
|
190
|
+
const tokenResponse =
|
|
191
|
+
await fetch(
|
|
192
|
+
`${API_BASE_URL}/api/auth/device/token`,
|
|
193
|
+
{
|
|
194
|
+
method: "POST",
|
|
195
|
+
|
|
196
|
+
headers: {
|
|
197
|
+
"Content-Type":
|
|
198
|
+
"application/json",
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
body: JSON.stringify({
|
|
202
|
+
deviceCode,
|
|
203
|
+
machineId:
|
|
204
|
+
hardwareId,
|
|
205
|
+
}),
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
let tokenData: any;
|
|
126
210
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
211
|
+
try {
|
|
212
|
+
tokenData =
|
|
213
|
+
await tokenResponse.json();
|
|
214
|
+
} catch {
|
|
215
|
+
s.stop(
|
|
216
|
+
pc.red(
|
|
217
|
+
"Authorization server returned invalid JSON.",
|
|
218
|
+
),
|
|
219
|
+
);
|
|
131
220
|
|
|
132
|
-
cancel(
|
|
221
|
+
cancel(
|
|
222
|
+
"Unable to read authorization response.",
|
|
223
|
+
);
|
|
133
224
|
|
|
134
225
|
process.exit(1);
|
|
135
226
|
}
|
|
227
|
+
|
|
228
|
+
// --------------------------------------------------
|
|
229
|
+
// SUCCESS
|
|
230
|
+
// --------------------------------------------------
|
|
231
|
+
|
|
232
|
+
if (
|
|
233
|
+
tokenResponse.ok &&
|
|
234
|
+
typeof tokenData.accessToken ===
|
|
235
|
+
"string" &&
|
|
236
|
+
tokenData.accessToken.length > 0
|
|
237
|
+
) {
|
|
238
|
+
token =
|
|
239
|
+
tokenData.accessToken;
|
|
240
|
+
|
|
241
|
+
// IMPORTANT:
|
|
242
|
+
// Stop the spinner immediately.
|
|
243
|
+
s.stop(
|
|
244
|
+
pc.green(
|
|
245
|
+
"Hardware signature linked & token verified!",
|
|
246
|
+
),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
// IMPORTANT:
|
|
250
|
+
// Leave the polling loop immediately.
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// --------------------------------------------------
|
|
255
|
+
// STILL WAITING
|
|
256
|
+
// --------------------------------------------------
|
|
257
|
+
|
|
258
|
+
if (
|
|
259
|
+
tokenData.error ===
|
|
260
|
+
"authorization_pending"
|
|
261
|
+
) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// --------------------------------------------------
|
|
266
|
+
// FAILED / EXPIRED
|
|
267
|
+
// --------------------------------------------------
|
|
268
|
+
|
|
269
|
+
s.stop(
|
|
270
|
+
pc.red(
|
|
271
|
+
"Authorization failed or expired.",
|
|
272
|
+
),
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
cancel(
|
|
276
|
+
`Reason: ${
|
|
277
|
+
tokenData.error ||
|
|
278
|
+
"Unknown authorization error"
|
|
279
|
+
}`,
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
process.exit(1);
|
|
136
283
|
}
|
|
137
284
|
|
|
138
285
|
// --------------------------------------------------
|
|
139
|
-
// 5.
|
|
286
|
+
// 5. Make absolutely sure token exists
|
|
140
287
|
// --------------------------------------------------
|
|
141
|
-
saveToken(token, hardwareId);
|
|
142
288
|
|
|
143
|
-
|
|
289
|
+
if (!token) {
|
|
290
|
+
throw new Error(
|
|
291
|
+
"Authorization completed without receiving an access token.",
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// --------------------------------------------------
|
|
296
|
+
// 6. Save CLI token + machine ID
|
|
297
|
+
// --------------------------------------------------
|
|
298
|
+
|
|
299
|
+
saveToken(
|
|
300
|
+
token,
|
|
301
|
+
hardwareId,
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
// --------------------------------------------------
|
|
305
|
+
// 7. Success
|
|
306
|
+
// --------------------------------------------------
|
|
144
307
|
|
|
145
308
|
outro(
|
|
146
|
-
`${pc.white(
|
|
147
|
-
|
|
309
|
+
`${pc.white(
|
|
310
|
+
"✔",
|
|
311
|
+
)} ${pc.bold(
|
|
312
|
+
"Terminal synced successfully",
|
|
313
|
+
)}\n` +
|
|
314
|
+
pc.gray(
|
|
315
|
+
"Secure device key and session token are ready.",
|
|
316
|
+
),
|
|
148
317
|
);
|
|
149
318
|
} catch (error: any) {
|
|
150
|
-
|
|
319
|
+
// Only stop the spinner if it is
|
|
320
|
+
// still running.
|
|
321
|
+
s.stop(
|
|
322
|
+
pc.red(
|
|
323
|
+
"An error occurred during login.",
|
|
324
|
+
),
|
|
325
|
+
);
|
|
151
326
|
|
|
152
|
-
cancel(
|
|
327
|
+
cancel(
|
|
328
|
+
pc.gray(
|
|
329
|
+
error?.message ||
|
|
330
|
+
"Unknown CLI Error",
|
|
331
|
+
),
|
|
332
|
+
);
|
|
153
333
|
|
|
154
334
|
process.exit(1);
|
|
155
335
|
}
|
|
156
|
-
});
|
|
336
|
+
});
|