@cognite/dune 0.1.2 → 0.2.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/_templates/app/new/config/biome.json.ejs.t +54 -0
- package/_templates/app/new/config/components.json.ejs.t +28 -0
- package/_templates/app/new/config/tailwind.config.js.ejs.t +15 -0
- package/_templates/app/new/{tsconfig.json.ejs.t → config/tsconfig.json.ejs.t} +4 -1
- package/_templates/app/new/config/vite.config.ts.ejs.t +27 -0
- package/_templates/app/new/cursor/data-modeling.mdc.ejs.t +1994 -0
- package/_templates/app/new/cursor/mcp.json.ejs.t +15 -0
- package/_templates/app/new/cursor/rules.mdc.ejs.t +12 -0
- package/_templates/app/new/root/PRD.md.ejs.t +5 -0
- package/_templates/app/new/{package.json.ejs.t → root/package.json.ejs.t} +11 -2
- package/_templates/app/new/{App.test.tsx.ejs.t → src/App.test.tsx.ejs.t} +5 -5
- package/_templates/app/new/{App.tsx.ejs.t → src/App.tsx.ejs.t} +2 -3
- package/_templates/app/new/src/lib/utils.ts.ejs.t +10 -0
- package/_templates/app/new/{main.tsx.ejs.t → src/main.tsx.ejs.t} +2 -0
- package/_templates/app/new/src/styles.css.ejs.t +25 -0
- package/bin/auth/authentication-flow.js +89 -0
- package/bin/auth/callback-server.js +181 -0
- package/bin/auth/certificate-manager.js +81 -0
- package/bin/auth/client-credentials.js +240 -0
- package/bin/auth/oauth-client.js +92 -0
- package/bin/cli.js +45 -5
- package/bin/deploy-command.js +246 -0
- package/bin/deploy-interactive-command.js +382 -0
- package/bin/utils/crypto.js +35 -0
- package/dist/deploy/index.d.ts +7 -0
- package/dist/deploy/index.js +43 -1
- package/package.json +3 -2
- package/src/deploy/application-deployer.ts +38 -0
- package/src/deploy/deploy.ts +8 -0
- package/_templates/app/new/biome.json.ejs.t +0 -25
- package/_templates/app/new/vite.config.ts.ejs.t +0 -15
- /package/_templates/app/new/{tsconfig.node.json.ejs.t → config/tsconfig.node.json.ejs.t} +0 -0
- /package/_templates/app/new/{vitest.config.ts.ejs.t → config/vitest.config.ts.ejs.t} +0 -0
- /package/_templates/app/new/{vitest.setup.ts.ejs.t → config/vitest.setup.ts.ejs.t} +0 -0
- /package/_templates/app/new/{app.json.ejs.t → root/app.json.ejs.t} +0 -0
- /package/_templates/app/new/{gitignore.ejs.t → root/gitignore.ejs.t} +0 -0
- /package/_templates/app/new/{index.html.ejs.t → root/index.html.ejs.t} +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: <%= name %>/.cursor/rules.mdc
|
|
3
|
+
---
|
|
4
|
+
---
|
|
5
|
+
description: Describes global rules
|
|
6
|
+
alwaysApply: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
- Use @aura shadcn components as much as possible. Stick with the style guide provided.
|
|
10
|
+
- Look at the PRD.md file at the root for requirements. If the file is empty, write some yourself as the first thing, and confirm with the user the requirements.
|
|
11
|
+
- The PRD.md file should contain the following: 1.1 Purpose, 1.2 Scope, 1.3 Target Audience, 2. Functional Requirements, 3. Non-Functional Requirements, 4. Data Models & Integration, 4.1 CDF Data Model, 4.1.1 Existing views, 4.1.2 New views, 4.1.3 Spaces, 5. User Stories
|
|
12
|
+
|
|
@@ -15,17 +15,22 @@ to: <%= name %>/package.json
|
|
|
15
15
|
"test:watch": "vitest",
|
|
16
16
|
"test:ui": "vitest --ui",
|
|
17
17
|
"lint": "biome check .",
|
|
18
|
-
"lint:fix": "biome check --write ."
|
|
18
|
+
"lint:fix": "biome check --write .",
|
|
19
|
+
"deploy": "pnpm run build && echo 'Deploying...' && echo 'Deployed!'",
|
|
20
|
+
"deploy-preview": "pnpm run build && echo 'Deploying preview...' && echo 'Deployed preview!'"
|
|
19
21
|
},
|
|
20
22
|
"dependencies": {
|
|
21
23
|
"@cognite/sdk": "^10.3.0",
|
|
22
24
|
"@cognite/dune": "^0.1.2",
|
|
23
25
|
"@tanstack/react-query": "^5.90.10",
|
|
26
|
+
"clsx": "^2.1.1",
|
|
24
27
|
"react": "^19.2.0",
|
|
25
|
-
"react-dom": "^19.2.0"
|
|
28
|
+
"react-dom": "^19.2.0",
|
|
29
|
+
"tailwind-merge": "^3.4.0"
|
|
26
30
|
},
|
|
27
31
|
"devDependencies": {
|
|
28
32
|
"@biomejs/biome": "^1.9.4",
|
|
33
|
+
"@tailwindcss/vite": "^4.1.17",
|
|
29
34
|
"@testing-library/jest-dom": "^6.6.3",
|
|
30
35
|
"@testing-library/react": "^16.1.0",
|
|
31
36
|
"@testing-library/user-event": "^14.5.2",
|
|
@@ -34,7 +39,11 @@ to: <%= name %>/package.json
|
|
|
34
39
|
"@types/react-dom": "^19.2.3",
|
|
35
40
|
"@vitejs/plugin-react": "^5.1.1",
|
|
36
41
|
"@vitest/ui": "^2.1.8",
|
|
42
|
+
"autoprefixer": "^10.4.22",
|
|
37
43
|
"happy-dom": "^20.0.2",
|
|
44
|
+
"postcss": "^8.5.6",
|
|
45
|
+
"shadcn": "^3.5.2",
|
|
46
|
+
"tailwindcss": "^4.1.17",
|
|
38
47
|
"typescript": "^5.0.0",
|
|
39
48
|
"vite": "^7.2.4",
|
|
40
49
|
"vite-plugin-mkcert": "^1.17.9",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
to: <%= name %>/src/App.test.tsx
|
|
3
3
|
---
|
|
4
|
-
import * as
|
|
4
|
+
import * as duneAuth from "@cognite/dune";
|
|
5
5
|
import { render, screen } from "@testing-library/react";
|
|
6
6
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
7
7
|
import App from "./App";
|
|
@@ -15,7 +15,7 @@ describe("App", () => {
|
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
it("renders loading state", () => {
|
|
18
|
-
vi.mocked(
|
|
18
|
+
vi.mocked(duneAuth.useDune).mockReturnValue({
|
|
19
19
|
sdk: { project: "test-project" } as any,
|
|
20
20
|
isLoading: true,
|
|
21
21
|
});
|
|
@@ -25,14 +25,14 @@ describe("App", () => {
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
it("renders app with project name", () => {
|
|
28
|
-
vi.mocked(
|
|
28
|
+
vi.mocked(duneAuth.useDune).mockReturnValue({
|
|
29
29
|
sdk: { project: "my-test-project" } as any,
|
|
30
30
|
isLoading: false,
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
render(<App />);
|
|
34
|
-
expect(screen.getByText("
|
|
35
|
-
expect(screen.getByText("
|
|
34
|
+
expect(screen.getByText("Welcome to my-test-project")).toBeInTheDocument();
|
|
35
|
+
expect(screen.getByText("Your Dune app is ready.")).toBeInTheDocument();
|
|
36
36
|
});
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -5,7 +5,6 @@ import { useDune } from "@cognite/dune";
|
|
|
5
5
|
|
|
6
6
|
function App() {
|
|
7
7
|
const { sdk, isLoading } = useDune();
|
|
8
|
-
const project = sdk.project;
|
|
9
8
|
|
|
10
9
|
if (isLoading) {
|
|
11
10
|
return <div>Loading...</div>;
|
|
@@ -13,8 +12,8 @@ function App() {
|
|
|
13
12
|
|
|
14
13
|
return (
|
|
15
14
|
<div>
|
|
16
|
-
<h1
|
|
17
|
-
<p>
|
|
15
|
+
<h1>Welcome to {sdk.project}</h1>
|
|
16
|
+
<p>Your Dune app is ready.</p>
|
|
18
17
|
</div>
|
|
19
18
|
);
|
|
20
19
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: <%= name %>/src/styles.css
|
|
3
|
+
---
|
|
4
|
+
@import "tailwindcss";
|
|
5
|
+
|
|
6
|
+
body {
|
|
7
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
8
|
+
min-height: 100vh;
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: center;
|
|
12
|
+
background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
h1 {
|
|
16
|
+
font-size: 2rem;
|
|
17
|
+
font-weight: 600;
|
|
18
|
+
color: #1a1a2e;
|
|
19
|
+
margin-bottom: 0.5rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
p {
|
|
23
|
+
color: #6b7280;
|
|
24
|
+
}
|
|
25
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Flow
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the complete OAuth 2.0 authentication flow with PKCE.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import open from "open";
|
|
8
|
+
import { CryptoUtils } from "../utils/crypto.js";
|
|
9
|
+
import { CallbackServer } from "./callback-server.js";
|
|
10
|
+
import { CertificateManager } from "./certificate-manager.js";
|
|
11
|
+
import { OAuthClient } from "./oauth-client.js";
|
|
12
|
+
|
|
13
|
+
export class AuthenticationFlow {
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} config - Authentication configuration
|
|
16
|
+
*/
|
|
17
|
+
constructor(config) {
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.oauthClient = new OAuthClient(config);
|
|
20
|
+
this.certManager = new CertificateManager(config.certDir);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validate client configuration
|
|
25
|
+
* @throws {Error} If configuration is invalid
|
|
26
|
+
*/
|
|
27
|
+
validateConfig() {
|
|
28
|
+
if (this.config.clientId === "your-client-id" || !this.config.clientId) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
"Please set the CDF_CLIENT_ID environment variable\n" +
|
|
31
|
+
" Example: export CDF_CLIENT_ID=<your-client-id>"
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Execute complete OAuth login flow
|
|
38
|
+
* @param {string} [organization] - Optional organization hint
|
|
39
|
+
* @returns {Promise<Object>} Access tokens
|
|
40
|
+
*/
|
|
41
|
+
async login(organization) {
|
|
42
|
+
console.log("🔐 Starting CDF login flow...\n");
|
|
43
|
+
|
|
44
|
+
this.validateConfig();
|
|
45
|
+
|
|
46
|
+
// Generate PKCE parameters (code verifier must be 43-128 chars per RFC 7636)
|
|
47
|
+
const codeVerifier = CryptoUtils.generateRandomString(64);
|
|
48
|
+
const codeChallenge = CryptoUtils.generateCodeChallenge(codeVerifier);
|
|
49
|
+
const state = CryptoUtils.generateRandomString(16);
|
|
50
|
+
|
|
51
|
+
// Get OpenID configuration
|
|
52
|
+
console.log(`📡 Fetching OpenID configuration from ${this.config.authority}...`);
|
|
53
|
+
const openIdConfig = await this.oauthClient.fetchOpenIdConfiguration();
|
|
54
|
+
|
|
55
|
+
// Build authorization URL
|
|
56
|
+
const authUrl = this.oauthClient.buildAuthorizationUrl(
|
|
57
|
+
openIdConfig.authorization_endpoint,
|
|
58
|
+
codeChallenge,
|
|
59
|
+
state,
|
|
60
|
+
organization
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Get SSL certificates
|
|
64
|
+
const sslOptions = this.certManager.getOrCreateCertificates();
|
|
65
|
+
|
|
66
|
+
// Start callback server
|
|
67
|
+
const callbackServer = new CallbackServer(this.config, sslOptions);
|
|
68
|
+
|
|
69
|
+
// Open browser
|
|
70
|
+
if (organization) {
|
|
71
|
+
console.log(`🏢 Organization: ${organization}`);
|
|
72
|
+
}
|
|
73
|
+
console.log("🚀 Opening browser for authentication...\n");
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
await open(authUrl);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error("❌ Failed to open browser automatically.");
|
|
79
|
+
console.error("Please open this URL manually:\n");
|
|
80
|
+
console.error(authUrl);
|
|
81
|
+
console.error("");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Wait for callback
|
|
85
|
+
const tokens = await callbackServer.start(state, codeVerifier, this.oauthClient);
|
|
86
|
+
|
|
87
|
+
return tokens;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Callback Server
|
|
3
|
+
*
|
|
4
|
+
* HTTPS server that handles OAuth 2.0 authorization callbacks.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import https from "node:https";
|
|
8
|
+
import { parse } from "node:url";
|
|
9
|
+
|
|
10
|
+
export class CallbackServer {
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} config - Server configuration
|
|
13
|
+
* @param {number} config.port - Port to listen on
|
|
14
|
+
* @param {number} config.loginTimeout - Timeout in milliseconds
|
|
15
|
+
* @param {Object} sslOptions - SSL certificate options
|
|
16
|
+
* @param {Buffer} sslOptions.key - SSL private key
|
|
17
|
+
* @param {Buffer} sslOptions.cert - SSL certificate
|
|
18
|
+
*/
|
|
19
|
+
constructor(config, sslOptions) {
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.sslOptions = sslOptions;
|
|
22
|
+
this.server = null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Generate HTML response page
|
|
27
|
+
* @param {string} type - Response type (success, error, security)
|
|
28
|
+
* @param {string} title - Page title
|
|
29
|
+
* @param {string} message - Message to display
|
|
30
|
+
* @returns {string} HTML string
|
|
31
|
+
*/
|
|
32
|
+
static generateHtml(type, title, message) {
|
|
33
|
+
const animation =
|
|
34
|
+
type === "success"
|
|
35
|
+
? `
|
|
36
|
+
<style>
|
|
37
|
+
@keyframes checkmark {
|
|
38
|
+
0% { transform: scale(0); }
|
|
39
|
+
50% { transform: scale(1.2); }
|
|
40
|
+
100% { transform: scale(1); }
|
|
41
|
+
}
|
|
42
|
+
h1 { animation: checkmark 0.5s ease-out; }
|
|
43
|
+
</style>
|
|
44
|
+
`
|
|
45
|
+
: "";
|
|
46
|
+
|
|
47
|
+
return `
|
|
48
|
+
<html>
|
|
49
|
+
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
50
|
+
<h1>${title}</h1>
|
|
51
|
+
<p>${message}</p>
|
|
52
|
+
<p>You can close this window.</p>
|
|
53
|
+
${animation}
|
|
54
|
+
</body>
|
|
55
|
+
</html>
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Handle OAuth callback request
|
|
61
|
+
* @param {Object} req - HTTP request
|
|
62
|
+
* @param {Object} res - HTTP response
|
|
63
|
+
* @param {string} expectedState - Expected CSRF state
|
|
64
|
+
* @param {string} codeVerifier - PKCE code verifier
|
|
65
|
+
* @param {Object} oauthClient - OAuth client instance
|
|
66
|
+
* @returns {Promise<{ shouldClose: boolean, tokens?: Object, error?: Error }>}
|
|
67
|
+
*/
|
|
68
|
+
async handleCallback(req, res, expectedState, codeVerifier, oauthClient) {
|
|
69
|
+
const parsedUrl = parse(req.url, true);
|
|
70
|
+
|
|
71
|
+
if (parsedUrl.pathname !== "/") {
|
|
72
|
+
res.writeHead(404);
|
|
73
|
+
res.end("Not found");
|
|
74
|
+
return { shouldClose: false };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const { code, state: returnedState, error } = parsedUrl.query;
|
|
78
|
+
|
|
79
|
+
// Handle authentication error
|
|
80
|
+
if (error) {
|
|
81
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
82
|
+
res.end(CallbackServer.generateHtml("error", "Authentication Error", String(error)));
|
|
83
|
+
return {
|
|
84
|
+
shouldClose: true,
|
|
85
|
+
error: new Error(`Authentication error: ${error}`),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Validate state to prevent CSRF
|
|
90
|
+
if (returnedState !== expectedState) {
|
|
91
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
92
|
+
res.end(
|
|
93
|
+
CallbackServer.generateHtml(
|
|
94
|
+
"security",
|
|
95
|
+
"Security Error",
|
|
96
|
+
"State mismatch. Possible CSRF attack."
|
|
97
|
+
)
|
|
98
|
+
);
|
|
99
|
+
return { shouldClose: true, error: new Error("State mismatch") };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Exchange code for tokens
|
|
103
|
+
try {
|
|
104
|
+
console.log("🔄 Exchanging authorization code for tokens...");
|
|
105
|
+
const openIdConfig = await oauthClient.fetchOpenIdConfiguration();
|
|
106
|
+
const tokens = await oauthClient.exchangeCodeForTokens(
|
|
107
|
+
openIdConfig.token_endpoint,
|
|
108
|
+
code,
|
|
109
|
+
codeVerifier
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
113
|
+
res.end(
|
|
114
|
+
CallbackServer.generateHtml(
|
|
115
|
+
"success",
|
|
116
|
+
"Login Successful!",
|
|
117
|
+
"You can close this window and return to the terminal."
|
|
118
|
+
)
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
return { shouldClose: true, tokens };
|
|
122
|
+
} catch (err) {
|
|
123
|
+
res.writeHead(500, { "Content-Type": "text/html" });
|
|
124
|
+
res.end(CallbackServer.generateHtml("error", "Token Exchange Failed", err.message));
|
|
125
|
+
return { shouldClose: true, error: err };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Start HTTPS server and wait for OAuth callback
|
|
131
|
+
* @param {string} expectedState - Expected CSRF state
|
|
132
|
+
* @param {string} codeVerifier - PKCE code verifier
|
|
133
|
+
* @param {Object} oauthClient - OAuth client instance
|
|
134
|
+
* @returns {Promise<Object>} Access tokens
|
|
135
|
+
*/
|
|
136
|
+
async start(expectedState, codeVerifier, oauthClient) {
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
this.server = https.createServer(this.sslOptions, async (req, res) => {
|
|
139
|
+
const result = await this.handleCallback(
|
|
140
|
+
req,
|
|
141
|
+
res,
|
|
142
|
+
expectedState,
|
|
143
|
+
codeVerifier,
|
|
144
|
+
oauthClient
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
if (result.shouldClose) {
|
|
148
|
+
this.server.close();
|
|
149
|
+
if (result.error) {
|
|
150
|
+
reject(result.error);
|
|
151
|
+
} else {
|
|
152
|
+
resolve(result.tokens);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Handle server errors
|
|
158
|
+
this.server.on("error", (error) => {
|
|
159
|
+
if (error.code === "EADDRINUSE") {
|
|
160
|
+
console.error(
|
|
161
|
+
`❌ Port ${this.config.port} is already in use. Please close the other application.`
|
|
162
|
+
);
|
|
163
|
+
} else {
|
|
164
|
+
console.error(`❌ Server error: ${error.message}`);
|
|
165
|
+
}
|
|
166
|
+
reject(error);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Start listening
|
|
170
|
+
this.server.listen(this.config.port, () => {
|
|
171
|
+
console.log(`🌐 Local HTTPS server started on https://localhost:${this.config.port}`);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Setup timeout
|
|
175
|
+
setTimeout(() => {
|
|
176
|
+
this.server.close();
|
|
177
|
+
reject(new Error("Login timeout - no response received within 5 minutes"));
|
|
178
|
+
}, this.config.loginTimeout);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSL Certificate Management
|
|
3
|
+
*
|
|
4
|
+
* Handles generation and loading of self-signed SSL certificates
|
|
5
|
+
* for the local HTTPS OAuth callback server.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync } from "node:child_process";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
|
|
12
|
+
export class CertificateManager {
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} certDir - Directory to store certificates
|
|
15
|
+
*/
|
|
16
|
+
constructor(certDir) {
|
|
17
|
+
this.certDir = certDir;
|
|
18
|
+
this.keyPath = path.join(certDir, "localhost-key.pem");
|
|
19
|
+
this.certPath = path.join(certDir, "localhost-cert.pem");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if valid certificates exist
|
|
24
|
+
* @returns {boolean} True if both key and certificate files exist
|
|
25
|
+
*/
|
|
26
|
+
certificatesExist() {
|
|
27
|
+
return fs.existsSync(this.keyPath) && fs.existsSync(this.certPath);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Load existing certificates from disk
|
|
32
|
+
* @returns {{ key: Buffer, cert: Buffer }} SSL certificate pair
|
|
33
|
+
*/
|
|
34
|
+
loadCertificates() {
|
|
35
|
+
return {
|
|
36
|
+
key: fs.readFileSync(this.keyPath),
|
|
37
|
+
cert: fs.readFileSync(this.certPath),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generate new self-signed certificate using OpenSSL
|
|
43
|
+
* @returns {{ key: Buffer, cert: Buffer }} Newly generated certificate pair
|
|
44
|
+
* @throws {Error} If OpenSSL is not available or certificate generation fails
|
|
45
|
+
*/
|
|
46
|
+
generateCertificate() {
|
|
47
|
+
console.log("🔐 Generating self-signed certificate for HTTPS...");
|
|
48
|
+
|
|
49
|
+
// Ensure directory exists
|
|
50
|
+
if (!fs.existsSync(this.certDir)) {
|
|
51
|
+
fs.mkdirSync(this.certDir, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const cmd = `openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' -keyout "${this.keyPath}" -out "${this.certPath}" -days 365 2>/dev/null`;
|
|
56
|
+
|
|
57
|
+
execSync(cmd, { stdio: "pipe" });
|
|
58
|
+
console.log("✅ Certificate generated and saved to ~/.cdf-login/\n");
|
|
59
|
+
|
|
60
|
+
return this.loadCertificates();
|
|
61
|
+
} catch (error) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
"Failed to generate self-signed certificate. Make sure openssl is installed.\n" +
|
|
64
|
+
" On macOS: openssl is pre-installed\n" +
|
|
65
|
+
" On Linux: sudo apt-get install openssl\n" +
|
|
66
|
+
" On Windows: Install OpenSSL or use WSL"
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get existing certificates or generate new ones
|
|
73
|
+
* @returns {{ key: Buffer, cert: Buffer }} SSL certificate pair
|
|
74
|
+
*/
|
|
75
|
+
getOrCreateCertificates() {
|
|
76
|
+
if (this.certificatesExist()) {
|
|
77
|
+
return this.loadCertificates();
|
|
78
|
+
}
|
|
79
|
+
return this.generateCertificate();
|
|
80
|
+
}
|
|
81
|
+
}
|