@vee-stack/delta-cli 2.0.4 → 2.0.5
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/dist/analyzer/commands/analyze.js +260 -0
- package/dist/analyzer/commands/config.js +83 -0
- package/dist/analyzer/commands/report.js +38 -0
- package/dist/analyzer/generators/report.generator.js +123 -0
- package/dist/analyzer/index.js +44 -0
- package/dist/analyzer/scanners/project.scanner.js +92 -0
- package/dist/analyzer/validators/contracts.validator.js +42 -0
- package/dist/analyzer/validators/maintainability.validator.js +40 -0
- package/dist/analyzer/validators/observability.validator.js +39 -0
- package/dist/analyzer/validators/performance.validator.js +42 -0
- package/dist/analyzer/validators/security.validator.js +66 -0
- package/dist/analyzer/validators/soc.validator.js +75 -0
- package/dist/apps/cli/src/analyzer/commands/analyze.js +256 -0
- package/dist/apps/cli/src/analyzer/commands/config.js +83 -0
- package/dist/apps/cli/src/analyzer/commands/report.js +38 -0
- package/dist/apps/cli/src/analyzer/generators/report.generator.js +123 -0
- package/dist/apps/cli/src/analyzer/index.js +44 -0
- package/dist/apps/cli/src/analyzer/scanners/project.scanner.js +92 -0
- package/dist/apps/cli/src/analyzer/validators/contracts.validator.js +42 -0
- package/dist/apps/cli/src/analyzer/validators/maintainability.validator.js +40 -0
- package/dist/apps/cli/src/analyzer/validators/observability.validator.js +39 -0
- package/dist/apps/cli/src/analyzer/validators/performance.validator.js +42 -0
- package/dist/apps/cli/src/analyzer/validators/security.validator.js +66 -0
- package/dist/apps/cli/src/analyzer/validators/soc.validator.js +75 -0
- package/dist/apps/cli/src/auth/secure-auth.js +312 -0
- package/dist/apps/cli/src/commands/analyze.js +286 -0
- package/dist/apps/cli/src/commands/auth-new.js +37 -0
- package/dist/apps/cli/src/commands/auth.js +122 -0
- package/dist/apps/cli/src/commands/config.js +49 -0
- package/dist/apps/cli/src/commands/deploy.js +6 -0
- package/dist/apps/cli/src/commands/init.js +47 -0
- package/dist/apps/cli/src/commands/logout.js +23 -0
- package/dist/apps/cli/src/commands/plugins.js +21 -0
- package/dist/apps/cli/src/commands/status.js +80 -0
- package/dist/apps/cli/src/commands/sync.js +6 -0
- package/dist/apps/cli/src/commands/whoami.js +115 -0
- package/dist/apps/cli/src/components/Dashboard.js +168 -0
- package/dist/apps/cli/src/components/DeltaApp.js +56 -0
- package/dist/apps/cli/src/components/UnifiedManager.js +324 -0
- package/dist/apps/cli/src/core/audit.js +184 -0
- package/dist/apps/cli/src/core/completion.js +294 -0
- package/dist/apps/cli/src/core/contracts.js +6 -0
- package/dist/apps/cli/src/core/engine.js +124 -0
- package/dist/apps/cli/src/core/exit-codes.js +71 -0
- package/dist/apps/cli/src/core/hooks.js +181 -0
- package/dist/apps/cli/src/core/index.js +7 -0
- package/dist/apps/cli/src/core/policy.js +115 -0
- package/dist/apps/cli/src/core/profiles.js +161 -0
- package/dist/apps/cli/src/core/wizard.js +203 -0
- package/dist/apps/cli/src/index.js +636 -0
- package/dist/apps/cli/src/interactive/index.js +11 -0
- package/dist/apps/cli/src/plugins/GitStatusPlugin.js +99 -0
- package/dist/apps/cli/src/providers/ai-provider.js +74 -0
- package/dist/apps/cli/src/providers/local-provider.js +302 -0
- package/dist/apps/cli/src/providers/remote-provider.js +100 -0
- package/dist/apps/cli/src/types/api.js +3 -0
- package/dist/apps/cli/src/ui.js +219 -0
- package/dist/apps/cli/src/welcome.js +81 -0
- package/dist/auth/secure-auth.js +418 -0
- package/dist/bundle.js +45 -45
- package/dist/commands/analyze.js +363 -0
- package/dist/commands/auth-new.js +37 -0
- package/dist/commands/auth.js +133 -0
- package/dist/commands/config.js +50 -0
- package/dist/commands/deploy.js +6 -0
- package/dist/commands/init.js +47 -0
- package/dist/commands/logout.js +30 -0
- package/dist/commands/plugins.js +21 -0
- package/dist/commands/status.js +82 -0
- package/dist/commands/sync.js +6 -0
- package/dist/commands/whoami.js +71 -0
- package/dist/components/Dashboard.js +169 -0
- package/dist/components/DeltaApp.js +57 -0
- package/dist/components/UnifiedManager.js +344 -0
- package/dist/core/audit.js +184 -0
- package/dist/core/completion.js +294 -0
- package/dist/core/contracts.js +6 -0
- package/dist/core/engine.js +124 -0
- package/dist/core/exit-codes.js +71 -0
- package/dist/core/hooks.js +181 -0
- package/dist/core/index.js +7 -0
- package/dist/core/policy.js +115 -0
- package/dist/core/profiles.js +161 -0
- package/dist/core/wizard.js +203 -0
- package/dist/index.js +387 -0
- package/dist/interactive/index.js +11 -0
- package/dist/packages/domain/src/constitution/contracts/index.js +43 -0
- package/dist/packages/domain/src/constitution/contracts/ts.rules.js +268 -0
- package/dist/packages/domain/src/constitution/index.js +139 -0
- package/dist/packages/domain/src/constitution/maintainability/index.js +43 -0
- package/dist/packages/domain/src/constitution/maintainability/ts.rules.js +344 -0
- package/dist/packages/domain/src/constitution/observability/index.js +43 -0
- package/dist/packages/domain/src/constitution/observability/ts.rules.js +307 -0
- package/dist/packages/domain/src/constitution/performance/index.js +43 -0
- package/dist/packages/domain/src/constitution/performance/ts.rules.js +325 -0
- package/dist/packages/domain/src/constitution/security/index.js +50 -0
- package/dist/packages/domain/src/constitution/security/ts.rules.js +267 -0
- package/dist/packages/domain/src/constitution/soc/index.js +43 -0
- package/dist/packages/domain/src/constitution/soc/ts.rules.js +360 -0
- package/dist/packages/domain/src/contracts/analysis.contract.js +18 -0
- package/dist/packages/domain/src/contracts/index.js +7 -0
- package/dist/packages/domain/src/contracts/projects.contract.js +18 -0
- package/dist/packages/domain/src/control/registry/rules.registry.js +29 -0
- package/dist/packages/domain/src/control/schemas/policies.js +6 -0
- package/dist/packages/domain/src/core/analysis/discovery.js +163 -0
- package/dist/packages/domain/src/core/analysis/engine.contract.js +298 -0
- package/dist/packages/domain/src/core/analysis/engine.js +77 -0
- package/dist/packages/domain/src/core/analysis/index.js +14 -0
- package/dist/packages/domain/src/core/analysis/orchestrator.js +242 -0
- package/dist/packages/domain/src/core/comparison/engine.js +29 -0
- package/dist/packages/domain/src/core/comparison/index.js +5 -0
- package/dist/packages/domain/src/core/documentation/index.js +5 -0
- package/dist/packages/domain/src/core/documentation/pipeline.js +41 -0
- package/dist/packages/domain/src/core/fs/adapter.js +111 -0
- package/dist/packages/domain/src/core/fs/index.js +5 -0
- package/dist/packages/domain/src/core/parser/unified-parser.js +166 -0
- package/dist/packages/domain/src/index.js +33 -0
- package/dist/packages/domain/src/plugin/registry.js +195 -0
- package/dist/packages/domain/src/plugin/types.js +6 -0
- package/dist/packages/domain/src/ports/analysis.engine.js +7 -0
- package/dist/packages/domain/src/ports/audit.logger.js +7 -0
- package/dist/packages/domain/src/ports/project.repository.js +7 -0
- package/dist/packages/domain/src/rules/index.js +134 -0
- package/dist/packages/domain/src/types/analysis.js +6 -0
- package/dist/packages/domain/src/types/errors.js +53 -0
- package/dist/packages/domain/src/types/fs.js +6 -0
- package/dist/packages/domain/src/types/index.js +7 -0
- package/dist/plugins/GitStatusPlugin.js +93 -0
- package/dist/providers/ai-provider.js +74 -0
- package/dist/providers/local-provider.js +304 -0
- package/dist/providers/remote-provider.js +100 -0
- package/dist/types/api.js +3 -0
- package/dist/ui.js +219 -0
- package/dist/welcome.js +81 -0
- package/package.json +18 -18
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure Authentication System with OAuth2 + Keychain Integration
|
|
3
|
+
* Features:
|
|
4
|
+
* - OAuth2 PKCE flow
|
|
5
|
+
* - Secure token storage in OS keychain
|
|
6
|
+
* - Automatic token refresh
|
|
7
|
+
* - Session management
|
|
8
|
+
* - PAT (Personal Access Token) support for CI/CD
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'fs/promises';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import * as os from 'os';
|
|
13
|
+
import fetch from 'node-fetch';
|
|
14
|
+
import open from 'open';
|
|
15
|
+
import { createServer } from 'http';
|
|
16
|
+
import { URL } from 'url';
|
|
17
|
+
import crypto from 'crypto';
|
|
18
|
+
import chalk from 'chalk';
|
|
19
|
+
import { printSuccess, printError, printInfo, printWarning, startSpinner, stopSpinner } from '../ui.js';
|
|
20
|
+
const CONFIG_DIR = path.join(os.homedir(), '.delta');
|
|
21
|
+
const TOKEN_FILE = path.join(CONFIG_DIR, 'tokens.enc');
|
|
22
|
+
const KEY_FILE = path.join(CONFIG_DIR, '.key');
|
|
23
|
+
// Derive encryption key from machine-specific data
|
|
24
|
+
async function getEncryptionKey() {
|
|
25
|
+
try {
|
|
26
|
+
const keyData = await fs.readFile(KEY_FILE, 'utf-8').catch(() => null);
|
|
27
|
+
if (keyData) {
|
|
28
|
+
return Buffer.from(keyData, 'hex');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// File doesn't exist, generate new key
|
|
33
|
+
}
|
|
34
|
+
const machineId = `${os.userInfo().username}@${os.hostname()}`;
|
|
35
|
+
const salt = 'delta-cli-v2-fixed-salt';
|
|
36
|
+
const key = crypto.pbkdf2Sync(machineId, salt, 100000, 32, 'sha256');
|
|
37
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
38
|
+
await fs.writeFile(KEY_FILE, key.toString('hex'), { mode: 0o600 });
|
|
39
|
+
return key;
|
|
40
|
+
}
|
|
41
|
+
// Encrypt data
|
|
42
|
+
async function encrypt(data) {
|
|
43
|
+
const key = await getEncryptionKey();
|
|
44
|
+
const iv = crypto.randomBytes(16);
|
|
45
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
46
|
+
let encrypted = cipher.update(data, 'utf-8', 'hex');
|
|
47
|
+
encrypted += cipher.final('hex');
|
|
48
|
+
const authTag = cipher.getAuthTag();
|
|
49
|
+
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
|
|
50
|
+
}
|
|
51
|
+
// Decrypt data
|
|
52
|
+
async function decrypt(encryptedData) {
|
|
53
|
+
try {
|
|
54
|
+
const key = await getEncryptionKey();
|
|
55
|
+
const parts = encryptedData.split(':');
|
|
56
|
+
if (parts.length !== 3)
|
|
57
|
+
return null;
|
|
58
|
+
const iv = Buffer.from(parts[0], 'hex');
|
|
59
|
+
const authTag = Buffer.from(parts[1], 'hex');
|
|
60
|
+
const encrypted = parts[2];
|
|
61
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
|
|
62
|
+
decipher.setAuthTag(authTag);
|
|
63
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf-8');
|
|
64
|
+
decrypted += decipher.final('utf-8');
|
|
65
|
+
return decrypted;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// PKCE Code Generator
|
|
72
|
+
function generatePKCE() {
|
|
73
|
+
const verifier = crypto.randomBytes(32).toString('base64url');
|
|
74
|
+
const challenge = crypto
|
|
75
|
+
.createHash('sha256')
|
|
76
|
+
.update(verifier)
|
|
77
|
+
.digest('base64url');
|
|
78
|
+
return { verifier, challenge };
|
|
79
|
+
}
|
|
80
|
+
// Secure Token Storage using encrypted file
|
|
81
|
+
export class SecureTokenStore {
|
|
82
|
+
static async saveTokens(tokens) {
|
|
83
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
84
|
+
const data = JSON.stringify({
|
|
85
|
+
access: tokens.access || null,
|
|
86
|
+
refresh: tokens.refresh || null,
|
|
87
|
+
updatedAt: new Date().toISOString(),
|
|
88
|
+
});
|
|
89
|
+
const encrypted = await encrypt(data);
|
|
90
|
+
await fs.writeFile(TOKEN_FILE, encrypted, { mode: 0o600 });
|
|
91
|
+
}
|
|
92
|
+
static async loadTokens() {
|
|
93
|
+
try {
|
|
94
|
+
const encrypted = await fs.readFile(TOKEN_FILE, 'utf-8');
|
|
95
|
+
const decrypted = await decrypt(encrypted);
|
|
96
|
+
if (!decrypted)
|
|
97
|
+
return { access: null, refresh: null };
|
|
98
|
+
const data = JSON.parse(decrypted);
|
|
99
|
+
return {
|
|
100
|
+
access: data.access || null,
|
|
101
|
+
refresh: data.refresh || null,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return { access: null, refresh: null };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
static async saveAccessToken(token) {
|
|
109
|
+
const tokens = await this.loadTokens();
|
|
110
|
+
await this.saveTokens({ access: token, refresh: tokens.refresh ?? undefined });
|
|
111
|
+
}
|
|
112
|
+
static async getAccessToken() {
|
|
113
|
+
const tokens = await this.loadTokens();
|
|
114
|
+
return tokens.access;
|
|
115
|
+
}
|
|
116
|
+
static async saveRefreshToken(token) {
|
|
117
|
+
const tokens = await this.loadTokens();
|
|
118
|
+
await this.saveTokens({ refresh: token, access: tokens.access ?? undefined });
|
|
119
|
+
}
|
|
120
|
+
static async getRefreshToken() {
|
|
121
|
+
const tokens = await this.loadTokens();
|
|
122
|
+
return tokens.refresh;
|
|
123
|
+
}
|
|
124
|
+
static async clearTokens() {
|
|
125
|
+
try {
|
|
126
|
+
await fs.unlink(TOKEN_FILE);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// File may not exist
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
static async hasTokens() {
|
|
133
|
+
const token = await this.getAccessToken();
|
|
134
|
+
return !!token;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// OAuth2 Flow Handler - Uses configurable API URL for local development
|
|
138
|
+
export async function startOAuthFlow(method = 'oauth') {
|
|
139
|
+
startSpinner('Starting OAuth2 authentication flow...');
|
|
140
|
+
try {
|
|
141
|
+
// Get API URL from environment or use default
|
|
142
|
+
const apiUrl = process.env.DELTA_API_URL || 'http://localhost:3000';
|
|
143
|
+
const { verifier, challenge } = generatePKCE();
|
|
144
|
+
const redirectUri = 'http://localhost:3456/callback';
|
|
145
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
146
|
+
// Build OAuth URL
|
|
147
|
+
const oauthUrl = new URL(`${apiUrl}/api/auth/authorize`);
|
|
148
|
+
oauthUrl.searchParams.set('response_type', 'code');
|
|
149
|
+
oauthUrl.searchParams.set('client_id', 'delta-cli');
|
|
150
|
+
oauthUrl.searchParams.set('redirect_uri', redirectUri);
|
|
151
|
+
oauthUrl.searchParams.set('code_challenge', challenge);
|
|
152
|
+
oauthUrl.searchParams.set('code_challenge_method', 'S256');
|
|
153
|
+
oauthUrl.searchParams.set('state', state);
|
|
154
|
+
oauthUrl.searchParams.set('scope', 'read write analyze');
|
|
155
|
+
if (method === 'github') {
|
|
156
|
+
oauthUrl.searchParams.set('provider', 'github');
|
|
157
|
+
}
|
|
158
|
+
else if (method === 'google') {
|
|
159
|
+
oauthUrl.searchParams.set('provider', 'google');
|
|
160
|
+
}
|
|
161
|
+
stopSpinner(true, 'OAuth server ready');
|
|
162
|
+
// Open browser
|
|
163
|
+
printInfo(`Opening browser for authentication...`);
|
|
164
|
+
printInfo(`URL: ${oauthUrl.toString().substring(0, 80)}...`);
|
|
165
|
+
await open(oauthUrl.toString());
|
|
166
|
+
// Start local server to capture callback
|
|
167
|
+
const authCode = await new Promise((resolve, reject) => {
|
|
168
|
+
const server = createServer(async (req, res) => {
|
|
169
|
+
// Get actual port from server address
|
|
170
|
+
const address = server.address();
|
|
171
|
+
const actualPort = typeof address === 'object' && address ? address.port : 3000;
|
|
172
|
+
const url = new URL(req.url || '/', `http://localhost:${actualPort}`);
|
|
173
|
+
if (url.pathname === '/callback') {
|
|
174
|
+
const code = url.searchParams.get('code');
|
|
175
|
+
const returnedState = url.searchParams.get('state');
|
|
176
|
+
const error = url.searchParams.get('error');
|
|
177
|
+
if (error) {
|
|
178
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
179
|
+
res.end(`
|
|
180
|
+
<html>
|
|
181
|
+
<body style="font-family: system-ui; text-align: center; padding: 50px;">
|
|
182
|
+
<h1 style="color: #e74c3c;">❌ Authentication Failed</h1>
|
|
183
|
+
<p>${error}</p>
|
|
184
|
+
<p>You can close this window.</p>
|
|
185
|
+
</body>
|
|
186
|
+
</html>
|
|
187
|
+
`);
|
|
188
|
+
reject(new Error(error));
|
|
189
|
+
server.close();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (!code || returnedState !== state) {
|
|
193
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
194
|
+
res.end(`
|
|
195
|
+
<html>
|
|
196
|
+
<body style="font-family: system-ui; text-align: center; padding: 50px;">
|
|
197
|
+
<h1 style="color: #e74c3c;">❌ Invalid Response</h1>
|
|
198
|
+
<p>You can close this window.</p>
|
|
199
|
+
</body>
|
|
200
|
+
</html>
|
|
201
|
+
`);
|
|
202
|
+
reject(new Error('Invalid OAuth response'));
|
|
203
|
+
server.close();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
207
|
+
res.end(`
|
|
208
|
+
<html>
|
|
209
|
+
<head>
|
|
210
|
+
<style>
|
|
211
|
+
body {
|
|
212
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
213
|
+
text-align: center;
|
|
214
|
+
padding: 50px;
|
|
215
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
216
|
+
color: white;
|
|
217
|
+
min-height: 100vh;
|
|
218
|
+
display: flex;
|
|
219
|
+
align-items: center;
|
|
220
|
+
justify-content: center;
|
|
221
|
+
}
|
|
222
|
+
.container {
|
|
223
|
+
background: rgba(255,255,255,0.1);
|
|
224
|
+
backdrop-filter: blur(10px);
|
|
225
|
+
padding: 40px;
|
|
226
|
+
border-radius: 20px;
|
|
227
|
+
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
228
|
+
}
|
|
229
|
+
h1 { margin: 0 0 20px; font-size: 48px; }
|
|
230
|
+
.check { font-size: 64px; margin-bottom: 20px; }
|
|
231
|
+
p { font-size: 18px; opacity: 0.9; }
|
|
232
|
+
</style>
|
|
233
|
+
</head>
|
|
234
|
+
<body>
|
|
235
|
+
<div class="container">
|
|
236
|
+
<div class="check">✓</div>
|
|
237
|
+
<h1>Successfully Signed In!</h1>
|
|
238
|
+
<p>You can close this window and return to the terminal.</p>
|
|
239
|
+
</div>
|
|
240
|
+
</body>
|
|
241
|
+
</html>
|
|
242
|
+
`);
|
|
243
|
+
resolve(code);
|
|
244
|
+
server.close();
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
// Try to listen on port 3456, or let OS assign available port if busy
|
|
248
|
+
server.listen(3456, '127.0.0.1', () => {
|
|
249
|
+
const address = server.address();
|
|
250
|
+
const actualPort = typeof address === 'object' && address ? address.port : 3456;
|
|
251
|
+
console.log(chalk.dim(` Waiting for authentication on port ${actualPort}...`));
|
|
252
|
+
});
|
|
253
|
+
// Handle port in use error by trying random port
|
|
254
|
+
server.on('error', (err) => {
|
|
255
|
+
if (err.code === 'EADDRINUSE') {
|
|
256
|
+
printWarning('Port 3456 is busy, trying alternative port...');
|
|
257
|
+
server.listen(0, '127.0.0.1'); // Let OS assign available port
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
reject(err);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
// Timeout after 5 minutes
|
|
264
|
+
setTimeout(() => {
|
|
265
|
+
server.close();
|
|
266
|
+
reject(new Error('Authentication timeout'));
|
|
267
|
+
}, 5 * 60 * 1000);
|
|
268
|
+
});
|
|
269
|
+
// Exchange code for tokens
|
|
270
|
+
startSpinner('Exchanging authorization code...');
|
|
271
|
+
const tokenResponse = await fetch(`${apiUrl}/api/auth/token`, {
|
|
272
|
+
method: 'POST',
|
|
273
|
+
headers: { 'Content-Type': 'application/json' },
|
|
274
|
+
body: JSON.stringify({
|
|
275
|
+
grant_type: 'authorization_code',
|
|
276
|
+
code: authCode,
|
|
277
|
+
redirect_uri: redirectUri,
|
|
278
|
+
client_id: 'delta-cli',
|
|
279
|
+
code_verifier: verifier,
|
|
280
|
+
}),
|
|
281
|
+
});
|
|
282
|
+
if (!tokenResponse.ok) {
|
|
283
|
+
throw new Error('Failed to exchange authorization code');
|
|
284
|
+
}
|
|
285
|
+
const tokens = await tokenResponse.json();
|
|
286
|
+
// Store tokens securely
|
|
287
|
+
await SecureTokenStore.saveAccessToken(tokens.access_token);
|
|
288
|
+
await SecureTokenStore.saveRefreshToken(tokens.refresh_token);
|
|
289
|
+
stopSpinner(true, 'Authentication successful!');
|
|
290
|
+
printSuccess('Successfully authenticated with Delta Cloud');
|
|
291
|
+
printInfo(`Token expires in ${tokens.expires_in} seconds`);
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
stopSpinner(false, 'Authentication failed');
|
|
296
|
+
printError('OAuth2 authentication failed', error.message);
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// PAT Authentication for CI/CD - Uses configurable API URL
|
|
301
|
+
export async function authenticateWithPAT(token) {
|
|
302
|
+
startSpinner('Validating Personal Access Token...');
|
|
303
|
+
try {
|
|
304
|
+
const apiUrl = process.env.DELTA_API_URL || 'http://localhost:3000';
|
|
305
|
+
// Validate token
|
|
306
|
+
const response = await fetch(`${apiUrl}/api/auth/verify`, {
|
|
307
|
+
headers: {
|
|
308
|
+
'Authorization': `Bearer ${token}`,
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
if (!response.ok) {
|
|
312
|
+
throw new Error('Invalid token');
|
|
313
|
+
}
|
|
314
|
+
const data = await response.json();
|
|
315
|
+
// Store token
|
|
316
|
+
await SecureTokenStore.saveAccessToken(token);
|
|
317
|
+
stopSpinner(true, 'Token validated');
|
|
318
|
+
printSuccess(`Authenticated as ${data.user.email}`);
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
stopSpinner(false, 'Token validation failed');
|
|
323
|
+
printError('PAT authentication failed', error.message);
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// Token Refresh - Uses configurable API URL
|
|
328
|
+
export async function refreshAccessToken() {
|
|
329
|
+
const refreshToken = await SecureTokenStore.getRefreshToken();
|
|
330
|
+
if (!refreshToken) {
|
|
331
|
+
printError('No refresh token available');
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const apiUrl = process.env.DELTA_API_URL || 'http://localhost:3000';
|
|
336
|
+
const response = await fetch(`${apiUrl}/api/auth/refresh`, {
|
|
337
|
+
method: 'POST',
|
|
338
|
+
headers: { 'Content-Type': 'application/json' },
|
|
339
|
+
body: JSON.stringify({
|
|
340
|
+
grant_type: 'refresh_token',
|
|
341
|
+
refresh_token: refreshToken,
|
|
342
|
+
client_id: 'delta-cli',
|
|
343
|
+
}),
|
|
344
|
+
});
|
|
345
|
+
if (!response.ok) {
|
|
346
|
+
throw new Error('Token refresh failed');
|
|
347
|
+
}
|
|
348
|
+
const tokens = await response.json();
|
|
349
|
+
await SecureTokenStore.saveAccessToken(tokens.access_token);
|
|
350
|
+
if (tokens.refresh_token) {
|
|
351
|
+
await SecureTokenStore.saveRefreshToken(tokens.refresh_token);
|
|
352
|
+
}
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
printError('Failed to refresh token', error.message);
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// Logout
|
|
361
|
+
export async function logout() {
|
|
362
|
+
startSpinner('Clearing credentials...');
|
|
363
|
+
try {
|
|
364
|
+
await SecureTokenStore.clearTokens();
|
|
365
|
+
stopSpinner(true, 'Logged out');
|
|
366
|
+
printSuccess('Successfully logged out');
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
stopSpinner(false, 'Logout failed');
|
|
370
|
+
printError('Failed to clear credentials', error.message);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// Auth Status - Uses configurable API URL
|
|
374
|
+
export async function getAuthStatus() {
|
|
375
|
+
const token = await SecureTokenStore.getAccessToken();
|
|
376
|
+
if (!token) {
|
|
377
|
+
return { authenticated: false };
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
const apiUrl = process.env.DELTA_API_URL || 'http://localhost:3000';
|
|
381
|
+
const response = await fetch(`${apiUrl}/api/auth/me`, {
|
|
382
|
+
headers: {
|
|
383
|
+
'Authorization': `Bearer ${token}`,
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
if (!response.ok) {
|
|
387
|
+
// Try to refresh token
|
|
388
|
+
const refreshed = await refreshAccessToken();
|
|
389
|
+
if (!refreshed) {
|
|
390
|
+
return { authenticated: false };
|
|
391
|
+
}
|
|
392
|
+
// Retry with new token
|
|
393
|
+
const newToken = await SecureTokenStore.getAccessToken();
|
|
394
|
+
const retryResponse = await fetch(`${apiUrl}/api/auth/me`, {
|
|
395
|
+
headers: {
|
|
396
|
+
'Authorization': `Bearer ${newToken}`,
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
if (!retryResponse.ok) {
|
|
400
|
+
return { authenticated: false };
|
|
401
|
+
}
|
|
402
|
+
const userData = await retryResponse.json();
|
|
403
|
+
return {
|
|
404
|
+
authenticated: true,
|
|
405
|
+
user: userData,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
const userData = await response.json();
|
|
409
|
+
return {
|
|
410
|
+
authenticated: true,
|
|
411
|
+
user: userData,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
return { authenticated: false };
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
//# sourceMappingURL=secure-auth.js.map
|