ds-01 1.0.13 → 1.1.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/.turbo/turbo-build.log +3 -2
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +16 -61
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +11 -45
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +20 -60
- package/dist/commands/logout.d.ts.map +1 -1
- package/dist/commands/logout.js +0 -2
- package/dist/native/macos-key-helper.swift +34 -71
- package/dist/utils/secureKeyStore.d.ts +0 -5
- package/dist/utils/secureKeyStore.d.ts.map +1 -1
- package/dist/utils/secureKeyStore.js +13 -28
- package/package.json +2 -2
- package/scripts/build-macos-helper.js +52 -0
- package/src/commands/add.ts +66 -210
- package/src/commands/init.ts +26 -51
- package/src/commands/login.ts +71 -244
- package/src/commands/logout.ts +0 -2
- package/src/native/macos-key-helper.swift +34 -71
- package/src/utils/secureKeyStore.ts +41 -88
- package/src/test-key.ts +0 -20
- package/src/utils/deviceKey.ts +0 -19
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
[6n[?9001h[?1004h[m]0;C:\WINDOWS\system32\cmd.exe[?25h[?25l
|
|
2
|
-
> ds-01@1.0.
|
|
3
|
-
> tsc[5;1H[?
|
|
2
|
+
> ds-01@1.0.14 build C:\Web-dev 2.0\Personal Projects\ds01\packages\cli
|
|
3
|
+
> tsc && node scripts/copy-native.js && node scripts/build-macos-helper.js[5;1H[?25hSkipping macOS native helper build.
|
|
4
|
+
[?9001l[?1004l
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+EpC,eAAO,MAAM,GAAG,SA+LZ,CAAC"}
|
package/dist/commands/add.js
CHANGED
|
@@ -9,9 +9,7 @@ import { signWithDeviceKey } from "../utils/secureKeyStore.js";
|
|
|
9
9
|
import machineIdPkg from "node-machine-id";
|
|
10
10
|
const { machineIdSync } = machineIdPkg;
|
|
11
11
|
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
12
|
-
//
|
|
13
|
-
// Helper: Verify Tailwind exists
|
|
14
|
-
// ---------------------------------------------------------
|
|
12
|
+
// function for tailwind dependency check
|
|
15
13
|
function checkTailwindInstallation() {
|
|
16
14
|
const targetDir = process.cwd();
|
|
17
15
|
const pkgJsonPath = path.join(targetDir, "package.json");
|
|
@@ -34,11 +32,10 @@ function checkTailwindInstallation() {
|
|
|
34
32
|
process.exit(1);
|
|
35
33
|
}
|
|
36
34
|
}
|
|
37
|
-
//
|
|
38
|
-
// Helper: Find dependencies that are actually missing
|
|
39
|
-
// ---------------------------------------------------------
|
|
35
|
+
// function to Find dependencies that are actually missing
|
|
40
36
|
function getMissingDependencies(dependencies) {
|
|
41
37
|
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
38
|
+
// I cannot check what is installed, so treat all required dependencies as missing
|
|
42
39
|
if (!fs.existsSync(packageJsonPath)) {
|
|
43
40
|
return dependencies;
|
|
44
41
|
}
|
|
@@ -49,9 +46,6 @@ function getMissingDependencies(dependencies) {
|
|
|
49
46
|
};
|
|
50
47
|
return dependencies.filter((dependency) => !installedDependencies[dependency]);
|
|
51
48
|
}
|
|
52
|
-
// ---------------------------------------------------------
|
|
53
|
-
// ADD COMMAND
|
|
54
|
-
// ---------------------------------------------------------
|
|
55
49
|
export const add = new Command()
|
|
56
50
|
.name("add")
|
|
57
51
|
.description("Add a component from DS01 to your project")
|
|
@@ -59,9 +53,7 @@ export const add = new Command()
|
|
|
59
53
|
.action(async (componentName) => {
|
|
60
54
|
console.log();
|
|
61
55
|
intro(`${pc.bgWhite(pc.black(pc.bold(" DS01 ")))} ${pc.gray("Adding Component")}`);
|
|
62
|
-
//
|
|
63
|
-
// 1. Validate project
|
|
64
|
-
// -----------------------------------------------------
|
|
56
|
+
// validatin
|
|
65
57
|
checkTailwindInstallation();
|
|
66
58
|
const cwd = process.cwd();
|
|
67
59
|
const configPath = path.join(cwd, "ds01.config.json");
|
|
@@ -70,9 +62,7 @@ export const add = new Command()
|
|
|
70
62
|
process.exit(1);
|
|
71
63
|
}
|
|
72
64
|
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
73
|
-
//
|
|
74
|
-
// 2. Authentication
|
|
75
|
-
// -----------------------------------------------------
|
|
65
|
+
// for auth
|
|
76
66
|
const token = getToken();
|
|
77
67
|
const machineId = machineIdSync();
|
|
78
68
|
if (!token || !machineId) {
|
|
@@ -82,9 +72,7 @@ export const add = new Command()
|
|
|
82
72
|
const s = spinner();
|
|
83
73
|
s.start(`Fetching ${pc.cyan(`<${componentName} />`)} from registry...`);
|
|
84
74
|
try {
|
|
85
|
-
//
|
|
86
|
-
// 3. Create request-specific signed message
|
|
87
|
-
// --------------------------------------------------
|
|
75
|
+
// Creating request-specific signed message
|
|
88
76
|
const timestamp = Date.now().toString();
|
|
89
77
|
const message = [
|
|
90
78
|
"GET",
|
|
@@ -92,14 +80,10 @@ export const add = new Command()
|
|
|
92
80
|
timestamp,
|
|
93
81
|
machineId,
|
|
94
82
|
].join("\n");
|
|
95
|
-
//
|
|
96
|
-
// 4. Sign request with secure device private key
|
|
97
|
-
// --------------------------------------------------
|
|
83
|
+
// Sign request with secure device private key
|
|
98
84
|
const signature = await signWithDeviceKey(new TextEncoder().encode(message));
|
|
99
85
|
const signatureBase64 = Buffer.from(signature).toString("base64");
|
|
100
|
-
//
|
|
101
|
-
// 5. Request component from registry
|
|
102
|
-
// --------------------------------------------------
|
|
86
|
+
// Request component from registry
|
|
103
87
|
const response = await fetch(`${API_BASE_URL}/api/registry/${componentName}`, {
|
|
104
88
|
method: "GET",
|
|
105
89
|
headers: {
|
|
@@ -109,33 +93,24 @@ export const add = new Command()
|
|
|
109
93
|
"X-DS01-Signature": signatureBase64,
|
|
110
94
|
},
|
|
111
95
|
});
|
|
112
|
-
//
|
|
113
|
-
// 6. Handle registry errors
|
|
114
|
-
// --------------------------------------------------
|
|
96
|
+
// Handle registry errors
|
|
115
97
|
if (!response.ok) {
|
|
116
98
|
const errorData = await response.json().catch(() => null);
|
|
117
99
|
s.stop(pc.red("Component fetch failed."));
|
|
118
|
-
cancel(errorData?.error ||
|
|
119
|
-
`Server returned HTTP ${response.status}`);
|
|
100
|
+
cancel(errorData?.error || `Server returned HTTP ${response.status}`);
|
|
120
101
|
process.exit(1);
|
|
121
102
|
}
|
|
122
103
|
const componentData = await response.json();
|
|
123
|
-
//
|
|
124
|
-
// 7. Create component directory
|
|
125
|
-
// --------------------------------------------------
|
|
104
|
+
// Create component directory
|
|
126
105
|
const targetDir = path.join(cwd, config.componentsPath, componentName);
|
|
127
106
|
if (!fs.existsSync(targetDir)) {
|
|
128
107
|
fs.mkdirSync(targetDir, {
|
|
129
108
|
recursive: true,
|
|
130
109
|
});
|
|
131
110
|
}
|
|
132
|
-
//
|
|
133
|
-
// 8. Extract bundled file names
|
|
134
|
-
// --------------------------------------------------
|
|
111
|
+
// Extract bundled file names
|
|
135
112
|
const bundledFileNames = componentData.files.map((file) => file.name.replace(/\.[^/.]+$/, ""));
|
|
136
|
-
//
|
|
137
|
-
// 9. Write component files
|
|
138
|
-
// --------------------------------------------------
|
|
113
|
+
// Write component files
|
|
139
114
|
for (const file of componentData.files) {
|
|
140
115
|
const filePath = path.join(targetDir, file.name);
|
|
141
116
|
let content = file.content;
|
|
@@ -146,9 +121,7 @@ export const add = new Command()
|
|
|
146
121
|
fs.writeFileSync(filePath, content);
|
|
147
122
|
}
|
|
148
123
|
s.stop(pc.green(`Downloaded ${componentData.files.length} files into ${pc.white(`/${config.componentsPath}/${componentName}`)}`));
|
|
149
|
-
//
|
|
150
|
-
// 10. Install ONLY missing dependencies
|
|
151
|
-
// --------------------------------------------------
|
|
124
|
+
// Install ONLY missing dependencies
|
|
152
125
|
const registryDependencies = Array.isArray(componentData.dependencies)
|
|
153
126
|
? componentData.dependencies
|
|
154
127
|
: [];
|
|
@@ -167,32 +140,14 @@ export const add = new Command()
|
|
|
167
140
|
note(`Please run: ${pc.cyan(`npm install ${depsToInstall}`)} manually.`, "Manual Action Required");
|
|
168
141
|
}
|
|
169
142
|
}
|
|
170
|
-
|
|
171
|
-
// 11. No dependencies needed
|
|
172
|
-
// --------------------------------------------------
|
|
173
|
-
// We intentionally don't show a spinner here.
|
|
174
|
-
//
|
|
175
|
-
// Example:
|
|
176
|
-
//
|
|
177
|
-
// DS01 requires ["motion"]
|
|
178
|
-
// package.json already contains "motion"
|
|
179
|
-
//
|
|
180
|
-
// → Nothing happens.
|
|
181
|
-
// → No npm install.
|
|
182
|
-
// → No unnecessary package changes.
|
|
183
|
-
// --------------------------------------------------
|
|
184
|
-
// 12. Success
|
|
185
|
-
// --------------------------------------------------
|
|
186
|
-
const mainFile = bundledFileNames.find((name) => name.toLowerCase() ===
|
|
187
|
-
componentName.toLowerCase()) || bundledFileNames[0];
|
|
143
|
+
const mainFile = bundledFileNames.find((name) => name.toLowerCase() === componentName.toLowerCase()) || bundledFileNames[0];
|
|
188
144
|
outro(`${pc.white("✔")} ${pc.bold(`Component <${componentName} /> is ready!`)}\n` +
|
|
189
145
|
pc.gray("Import it: ") +
|
|
190
146
|
pc.cyan(`import { ${mainFile} } from "@/${config.componentsPath}/${componentName}/${mainFile}"`));
|
|
191
147
|
}
|
|
192
148
|
catch (error) {
|
|
193
149
|
s.stop(pc.red("An error occurred during injection."));
|
|
194
|
-
cancel(pc.gray(error?.message ||
|
|
195
|
-
"Unknown CLI Error"));
|
|
150
|
+
cancel(pc.gray(error?.message || "Unknown CLI Error"));
|
|
196
151
|
process.exit(1);
|
|
197
152
|
}
|
|
198
153
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,eAAO,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,eAAO,MAAM,IAAI,SAmEb,CAAC"}
|
package/dist/commands/init.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { intro, outro, spinner,
|
|
4
|
+
import { intro, outro, spinner, cancel } from "@clack/prompts";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
export const init = new Command()
|
|
7
7
|
.name("init")
|
|
@@ -13,72 +13,38 @@ export const init = new Command()
|
|
|
13
13
|
const cwd = process.cwd();
|
|
14
14
|
const packageJsonPath = path.join(cwd, "package.json");
|
|
15
15
|
const tsconfigPath = path.join(cwd, "tsconfig.json");
|
|
16
|
-
//
|
|
16
|
+
// runs only if package file missing
|
|
17
17
|
if (!fs.existsSync(packageJsonPath)) {
|
|
18
18
|
s.stop("Environment check failed.");
|
|
19
19
|
cancel("No package.json found. Please run this command in the root of your project.");
|
|
20
20
|
process.exit(1);
|
|
21
21
|
}
|
|
22
22
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
23
|
-
const deps = {
|
|
24
|
-
|
|
23
|
+
const deps = {
|
|
24
|
+
...packageJson.dependencies,
|
|
25
|
+
...packageJson.devDependencies,
|
|
26
|
+
};
|
|
27
|
+
// Check strictly for React or Next.js
|
|
25
28
|
if (!deps.react && !deps.next) {
|
|
26
29
|
s.stop("Environment check failed.");
|
|
27
30
|
cancel("DS01 strictly supports React and Next.js environments. Please run this in a supported project.");
|
|
28
31
|
process.exit(1);
|
|
29
32
|
}
|
|
30
|
-
//
|
|
33
|
+
// Check strictly for TypeScript
|
|
31
34
|
if (!fs.existsSync(tsconfigPath)) {
|
|
32
35
|
s.stop("Environment check failed.");
|
|
33
36
|
cancel("DS01 requires TypeScript. No tsconfig.json found in the project root.");
|
|
34
37
|
process.exit(1);
|
|
35
38
|
}
|
|
36
39
|
s.stop(chalk.green("✔ React and TypeScript detected!"));
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
message: "Where is your global CSS file?",
|
|
40
|
-
placeholder: "app/globals.css",
|
|
41
|
-
initialValue: "app/globals.css",
|
|
42
|
-
});
|
|
43
|
-
if (isCancel(cssPath)) {
|
|
44
|
-
cancel("Setup cancelled.");
|
|
45
|
-
process.exit(0);
|
|
46
|
-
}
|
|
47
|
-
// 5. Inject Dark Mode / Glassmorphic CSS variables
|
|
48
|
-
const fullCssPath = path.join(cwd, cssPath);
|
|
49
|
-
if (fs.existsSync(fullCssPath)) {
|
|
50
|
-
const cssContent = fs.readFileSync(fullCssPath, "utf-8");
|
|
51
|
-
if (!cssContent.includes("/* ds01-theme-start */")) {
|
|
52
|
-
const variables = `
|
|
53
|
-
/* ds01-theme-start */
|
|
54
|
-
@layer base {
|
|
55
|
-
:root {
|
|
56
|
-
--ds-background: #0a0a0a; /* Deep charcoal */
|
|
57
|
-
--ds-foreground: #ffffff;
|
|
58
|
-
--ds-glass-border: rgba(255, 255, 255, 0.08);
|
|
59
|
-
--ds-glass-blur: blur(16px);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
/* ds01-theme-end */
|
|
63
|
-
`;
|
|
64
|
-
fs.appendFileSync(fullCssPath, variables);
|
|
65
|
-
console.log(chalk.green(`✔ Injected design tokens into ${cssPath}`));
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
console.log(chalk.gray(`- Design tokens already exist in ${cssPath}`));
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
console.log(chalk.yellow(`⚠ Could not find ${cssPath}. You may need to add the CSS variables manually.`));
|
|
73
|
-
}
|
|
74
|
-
// 6. Create the local configuration file so the CLI remembers this project
|
|
40
|
+
// Create the local configuration file so the CLI remembers this project
|
|
41
|
+
//! Need to be understand
|
|
75
42
|
const configPath = path.join(cwd, "ds01.config.json");
|
|
76
43
|
const configData = {
|
|
77
44
|
$schema: "https://ds-01.vercel.app/schema.json",
|
|
78
45
|
style: "default",
|
|
79
46
|
typescript: true,
|
|
80
|
-
|
|
81
|
-
componentsPath: "components/ui" // This is where we will inject components later
|
|
47
|
+
componentsPath: "components/ui", // This is where we will inject components later
|
|
82
48
|
};
|
|
83
49
|
fs.writeFileSync(configPath, JSON.stringify(configData, null, 2));
|
|
84
50
|
console.log(chalk.green(`✔ Created ds01.config.json`));
|
|
@@ -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;AAepC,eAAO,MAAM,KAAK,SAyJd,CAAC"}
|
package/dist/commands/login.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { intro, outro, spinner, note, cancel
|
|
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 ||
|
|
9
|
-
"https://ds-01.vercel.app";
|
|
8
|
+
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
10
9
|
const { machineIdSync } = machineIdPkg;
|
|
11
10
|
export const login = new Command()
|
|
12
11
|
.name("login")
|
|
@@ -16,19 +15,12 @@ export const login = new Command()
|
|
|
16
15
|
intro(`${pc.bgWhite(pc.black(pc.bold(" DS01 ")))} ${pc.gray("v1.0.0 — Terminal Authentication")}`);
|
|
17
16
|
const s = spinner();
|
|
18
17
|
try {
|
|
19
|
-
// ==================================================
|
|
20
|
-
// 1. CREATE / LOAD SECURE DEVICE KEY
|
|
21
|
-
// ==================================================
|
|
22
18
|
s.start("Preparing secure device identity...");
|
|
23
19
|
await createDeviceKey();
|
|
24
20
|
const publicKey = await getDevicePublicKey();
|
|
25
21
|
const hardwareId = machineIdSync();
|
|
26
|
-
const publicKeyType = process.platform === "win32"
|
|
27
|
-
|
|
28
|
-
: "macos-ec";
|
|
29
|
-
// ==================================================
|
|
30
|
-
// 2. REQUEST DEVICE CODE
|
|
31
|
-
// ==================================================
|
|
22
|
+
const publicKeyType = process.platform === "win32" ? "windows-rsa" : "macos-ec";
|
|
23
|
+
// Req to my server with machineId and certificate for now but eventually will send the publicKey
|
|
32
24
|
s.start("Requesting pairing code...");
|
|
33
25
|
const initResponse = await fetch(`${API_BASE_URL}/api/auth/device/code`, {
|
|
34
26
|
method: "POST",
|
|
@@ -46,16 +38,12 @@ export const login = new Command()
|
|
|
46
38
|
cancel(`Server responded with HTTP ${initResponse.status}`);
|
|
47
39
|
process.exit(1);
|
|
48
40
|
}
|
|
49
|
-
const { deviceCode, userCode, verificationUrl, interval
|
|
50
|
-
if (!deviceCode ||
|
|
51
|
-
!userCode ||
|
|
52
|
-
!verificationUrl) {
|
|
41
|
+
const { deviceCode, userCode, verificationUrl, interval } = await initResponse.json();
|
|
42
|
+
if (!deviceCode || !userCode || !verificationUrl) {
|
|
53
43
|
throw new Error("Authorization server returned an invalid device response.");
|
|
54
44
|
}
|
|
55
45
|
s.stop(pc.green("Secure device identity registered."));
|
|
56
|
-
//
|
|
57
|
-
// 3. SHOW BROWSER AUTHORIZATION
|
|
58
|
-
// ==================================================
|
|
46
|
+
// Opening users browser with verification URL
|
|
59
47
|
note(`${pc.bold("Pairing Code:")} ${pc.cyan(pc.bold(` ${userCode} `))}\n` +
|
|
60
48
|
`${pc.bold("Verification URL:")} ${pc.underline(verificationUrl)}`, "Action Required");
|
|
61
49
|
try {
|
|
@@ -64,9 +52,7 @@ export const login = new Command()
|
|
|
64
52
|
catch {
|
|
65
53
|
// Browser launch failure is non-fatal.
|
|
66
54
|
}
|
|
67
|
-
//
|
|
68
|
-
// 4. POLL FOR AUTHORIZATION
|
|
69
|
-
// ==================================================
|
|
55
|
+
// we polling to one of my server endpoint to check wether the temporary req is approved or not ??
|
|
70
56
|
s.start("Waiting for web authorization...");
|
|
71
57
|
const pollInterval = (interval || 5) * 1000;
|
|
72
58
|
let token = null;
|
|
@@ -84,65 +70,40 @@ export const login = new Command()
|
|
|
84
70
|
});
|
|
85
71
|
let tokenData;
|
|
86
72
|
try {
|
|
87
|
-
tokenData =
|
|
88
|
-
await tokenResponse.json();
|
|
73
|
+
tokenData = await tokenResponse.json();
|
|
89
74
|
}
|
|
90
75
|
catch {
|
|
91
76
|
s.stop(pc.red("Authorization server returned invalid JSON."));
|
|
92
77
|
cancel("Unable to read authorization response.");
|
|
93
78
|
process.exit(1);
|
|
94
79
|
}
|
|
95
|
-
//
|
|
96
|
-
// AUTHORIZATION SUCCESS
|
|
97
|
-
// ==================================================
|
|
80
|
+
// Auth success stop polling
|
|
98
81
|
if (tokenResponse.ok &&
|
|
99
|
-
typeof tokenData.accessToken ===
|
|
100
|
-
"string" &&
|
|
82
|
+
typeof tokenData.accessToken === "string" &&
|
|
101
83
|
tokenData.accessToken.length > 0) {
|
|
102
|
-
token =
|
|
103
|
-
tokenData.accessToken;
|
|
84
|
+
token = tokenData.accessToken;
|
|
104
85
|
break;
|
|
105
86
|
}
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
// ==================================================
|
|
109
|
-
if (tokenData.error ===
|
|
110
|
-
"authorization_pending") {
|
|
87
|
+
// Still waiting keep polling
|
|
88
|
+
if (tokenData.error === "authorization_pending") {
|
|
111
89
|
continue;
|
|
112
90
|
}
|
|
113
|
-
//
|
|
114
|
-
// AUTHORIZATION FAILED
|
|
115
|
-
// ==================================================
|
|
91
|
+
// Authorization Failed
|
|
116
92
|
s.stop(pc.red("Authorization failed or expired."));
|
|
117
|
-
cancel(`Reason: ${tokenData.error ||
|
|
118
|
-
"Unknown authorization error"}`);
|
|
93
|
+
cancel(`Reason: ${tokenData.error || "Unknown authorization error"}`);
|
|
119
94
|
process.exit(1);
|
|
120
95
|
}
|
|
121
|
-
//
|
|
122
|
-
// 5. STOP POLLING SPINNER
|
|
123
|
-
// ==================================================
|
|
96
|
+
// outside the loop stop the spinner
|
|
124
97
|
s.stop(pc.green("Hardware signature linked & token verified!"));
|
|
125
|
-
//
|
|
126
|
-
// 6. ENSURE TOKEN EXISTS
|
|
127
|
-
// ==================================================
|
|
98
|
+
// ENSURE TOKEN EXISTS
|
|
128
99
|
if (!token) {
|
|
129
100
|
throw new Error("Authorization completed without receiving an access token.");
|
|
130
101
|
}
|
|
131
|
-
// ==================================================
|
|
132
|
-
// 7. SAVE TOKEN
|
|
133
|
-
// ==================================================
|
|
134
102
|
saveToken(token, hardwareId);
|
|
135
|
-
// ==================================================
|
|
136
|
-
// 8. SUCCESS
|
|
137
|
-
// ==================================================
|
|
138
103
|
outro(`${pc.white("✔")} ${pc.bold("Terminal synced successfully")}\n` +
|
|
139
104
|
pc.gray("Secure device key and session token are ready."));
|
|
140
|
-
//
|
|
141
|
-
// 9. HARD EXIT
|
|
142
|
-
// ==================================================
|
|
143
|
-
//
|
|
105
|
+
// Hard Exit
|
|
144
106
|
// The login command is completely finished.
|
|
145
|
-
//
|
|
146
107
|
// This prevents any lingering CLI handles
|
|
147
108
|
// (spinner timers, readline handles, etc.)
|
|
148
109
|
// from keeping the Node process alive.
|
|
@@ -151,8 +112,7 @@ export const login = new Command()
|
|
|
151
112
|
}
|
|
152
113
|
catch (error) {
|
|
153
114
|
s.stop(pc.red("An error occurred during login."));
|
|
154
|
-
cancel(pc.gray(error?.message ||
|
|
155
|
-
"Unknown CLI Error"));
|
|
115
|
+
cancel(pc.gray(error?.message || "Unknown CLI Error"));
|
|
156
116
|
process.exit(1);
|
|
157
117
|
}
|
|
158
118
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,eAAO,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,eAAO,MAAM,MAAM,SA+Cf,CAAC"}
|
package/dist/commands/logout.js
CHANGED
|
@@ -32,9 +32,7 @@ export const logout = new Command()
|
|
|
32
32
|
}
|
|
33
33
|
// NEW: Delete local credentials only after the server confirms
|
|
34
34
|
// that the CLI session has been revoked.
|
|
35
|
-
console.log("✅ Server confirmed CLI session was revoked.");
|
|
36
35
|
deleteToken();
|
|
37
|
-
console.log("✅ Local credentials deleted.");
|
|
38
36
|
spinner.succeed(chalk.green("Successfully logged out."));
|
|
39
37
|
console.log(chalk.gray("Your CLI session has been revoked and local credentials have been cleared."));
|
|
40
38
|
}
|
|
@@ -8,17 +8,20 @@ func fail(_ message: String) -> Never {
|
|
|
8
8
|
exit(1)
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
// Find
|
|
11
|
+
// Find our existing private key in the current user's Keychain.
|
|
12
12
|
func findPrivateKey() -> SecKey? {
|
|
13
13
|
let query: [String: Any] = [
|
|
14
|
-
kSecClass as String:
|
|
14
|
+
kSecClass as String:
|
|
15
|
+
kSecClassKey,
|
|
16
|
+
|
|
15
17
|
kSecAttrApplicationTag as String:
|
|
16
18
|
keyTag.data(using: .utf8)!,
|
|
19
|
+
|
|
17
20
|
kSecAttrKeyType as String:
|
|
18
21
|
kSecAttrKeyTypeECSECPrimeRandom,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
|
|
23
|
+
kSecReturnRef as String:
|
|
24
|
+
true
|
|
22
25
|
]
|
|
23
26
|
|
|
24
27
|
var result: CFTypeRef?
|
|
@@ -35,54 +38,16 @@ func findPrivateKey() -> SecKey? {
|
|
|
35
38
|
return (result as! SecKey)
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
|
|
42
|
+
// Create a persistent EC P-256 private key.
|
|
38
43
|
func createKey() {
|
|
39
44
|
|
|
40
|
-
//
|
|
45
|
+
// Don't create a second key if one already exists.
|
|
41
46
|
if findPrivateKey() != nil {
|
|
42
47
|
print("EXISTS")
|
|
43
48
|
return
|
|
44
49
|
}
|
|
45
50
|
|
|
46
|
-
// Keychain access control.
|
|
47
|
-
//
|
|
48
|
-
// WhenUnlockedThisDeviceOnly means:
|
|
49
|
-
// - available only while the device is unlocked
|
|
50
|
-
// - cannot be restored onto another device
|
|
51
|
-
//
|
|
52
|
-
// privateKeyUsage means the key can be used for signing.
|
|
53
|
-
var accessError: Unmanaged<CFError>?
|
|
54
|
-
|
|
55
|
-
guard let access = SecAccessControlCreateWithFlags(
|
|
56
|
-
nil,
|
|
57
|
-
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
58
|
-
.privateKeyUsage,
|
|
59
|
-
&accessError
|
|
60
|
-
) else {
|
|
61
|
-
|
|
62
|
-
if let error = accessError {
|
|
63
|
-
fail(
|
|
64
|
-
"Unable to create Keychain access control: " +
|
|
65
|
-
error.takeRetainedValue().localizedDescription
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
fail("Unable to create Keychain access control.")
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
let privateAttributes: [String: Any] = [
|
|
73
|
-
|
|
74
|
-
// Permanently store the private key.
|
|
75
|
-
kSecAttrIsPermanent as String: true,
|
|
76
|
-
|
|
77
|
-
// Identifier used to find the key later.
|
|
78
|
-
kSecAttrApplicationTag as String:
|
|
79
|
-
keyTag.data(using: .utf8)!,
|
|
80
|
-
|
|
81
|
-
// Access rules for the private key.
|
|
82
|
-
kSecAttrAccessControl as String:
|
|
83
|
-
access
|
|
84
|
-
]
|
|
85
|
-
|
|
86
51
|
let attributes: [String: Any] = [
|
|
87
52
|
|
|
88
53
|
// EC P-256 key.
|
|
@@ -92,14 +57,15 @@ func createKey() {
|
|
|
92
57
|
kSecAttrKeySizeInBits as String:
|
|
93
58
|
256,
|
|
94
59
|
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
60
|
+
// Store the private key permanently in Keychain.
|
|
61
|
+
kSecPrivateKeyAttrs as String: [
|
|
62
|
+
|
|
63
|
+
kSecAttrIsPermanent as String:
|
|
64
|
+
true,
|
|
99
65
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
66
|
+
kSecAttrApplicationTag as String:
|
|
67
|
+
keyTag.data(using: .utf8)!
|
|
68
|
+
]
|
|
103
69
|
]
|
|
104
70
|
|
|
105
71
|
var error: Unmanaged<CFError>?
|
|
@@ -111,28 +77,30 @@ func createKey() {
|
|
|
111
77
|
|
|
112
78
|
if let error {
|
|
113
79
|
fail(
|
|
114
|
-
"
|
|
80
|
+
"Keychain key creation failed: " +
|
|
115
81
|
error.takeRetainedValue().localizedDescription
|
|
116
82
|
)
|
|
117
83
|
}
|
|
118
84
|
|
|
119
|
-
fail("
|
|
85
|
+
fail("Keychain key creation failed.")
|
|
120
86
|
}
|
|
121
87
|
|
|
122
88
|
print("CREATED")
|
|
123
89
|
}
|
|
124
90
|
|
|
125
91
|
|
|
126
|
-
// Return
|
|
92
|
+
// Return only the public key.
|
|
127
93
|
//
|
|
128
94
|
// The private key is never exported.
|
|
129
95
|
func publicKey() {
|
|
130
96
|
|
|
131
97
|
guard let privateKey = findPrivateKey() else {
|
|
132
|
-
fail("DS01
|
|
98
|
+
fail("DS01 device key not found.")
|
|
133
99
|
}
|
|
134
100
|
|
|
135
|
-
guard let publicKey = SecKeyCopyPublicKey(
|
|
101
|
+
guard let publicKey = SecKeyCopyPublicKey(
|
|
102
|
+
privateKey
|
|
103
|
+
) else {
|
|
136
104
|
fail("Unable to obtain public key.")
|
|
137
105
|
}
|
|
138
106
|
|
|
@@ -159,11 +127,11 @@ func publicKey() {
|
|
|
159
127
|
}
|
|
160
128
|
|
|
161
129
|
|
|
162
|
-
// Sign data using the
|
|
130
|
+
// Sign data using the Keychain private key.
|
|
163
131
|
func sign(_ base64Data: String) {
|
|
164
132
|
|
|
165
133
|
guard let privateKey = findPrivateKey() else {
|
|
166
|
-
fail("DS01
|
|
134
|
+
fail("DS01 device key not found.")
|
|
167
135
|
}
|
|
168
136
|
|
|
169
137
|
guard let data = Data(
|
|
@@ -180,9 +148,7 @@ func sign(_ base64Data: String) {
|
|
|
180
148
|
.sign,
|
|
181
149
|
algorithm
|
|
182
150
|
) else {
|
|
183
|
-
fail(
|
|
184
|
-
"Secure Enclave key does not support signing."
|
|
185
|
-
)
|
|
151
|
+
fail("Device key does not support signing.")
|
|
186
152
|
}
|
|
187
153
|
|
|
188
154
|
var error: Unmanaged<CFError>?
|
|
@@ -196,12 +162,12 @@ func sign(_ base64Data: String) {
|
|
|
196
162
|
|
|
197
163
|
if let error {
|
|
198
164
|
fail(
|
|
199
|
-
"
|
|
165
|
+
"Signing failed: " +
|
|
200
166
|
error.takeRetainedValue().localizedDescription
|
|
201
167
|
)
|
|
202
168
|
}
|
|
203
169
|
|
|
204
|
-
fail("
|
|
170
|
+
fail("Signing failed.")
|
|
205
171
|
}
|
|
206
172
|
|
|
207
173
|
print(
|
|
@@ -210,7 +176,7 @@ func sign(_ base64Data: String) {
|
|
|
210
176
|
}
|
|
211
177
|
|
|
212
178
|
|
|
213
|
-
//
|
|
179
|
+
// Delete the DS01 private key from Keychain.
|
|
214
180
|
func deleteKey() {
|
|
215
181
|
|
|
216
182
|
let query: [String: Any] = [
|
|
@@ -221,10 +187,7 @@ func deleteKey() {
|
|
|
221
187
|
keyTag.data(using: .utf8)!,
|
|
222
188
|
|
|
223
189
|
kSecAttrKeyType as String:
|
|
224
|
-
kSecAttrKeyTypeECSECPrimeRandom
|
|
225
|
-
|
|
226
|
-
kSecAttrTokenID as String:
|
|
227
|
-
kSecAttrTokenIDSecureEnclave
|
|
190
|
+
kSecAttrKeyTypeECSECPrimeRandom
|
|
228
191
|
]
|
|
229
192
|
|
|
230
193
|
let status = SecItemDelete(
|
|
@@ -235,7 +198,7 @@ func deleteKey() {
|
|
|
235
198
|
status != errSecItemNotFound {
|
|
236
199
|
|
|
237
200
|
fail(
|
|
238
|
-
"Unable to delete DS01
|
|
201
|
+
"Unable to delete DS01 device key."
|
|
239
202
|
)
|
|
240
203
|
}
|
|
241
204
|
|