shiva-code 0.8.18 → 0.8.19

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.
@@ -19,853 +19,6 @@ import {
19
19
  import { Command } from "commander";
20
20
  import inquirer2 from "inquirer";
21
21
 
22
- // src/services/auth/auth.ts
23
- import inquirer from "inquirer";
24
- import open from "open";
25
- import http from "http";
26
- import { URL } from "url";
27
-
28
- // src/services/auth/two-factor.ts
29
- import * as crypto from "crypto";
30
- function generateSecret(length = 20) {
31
- const buffer = crypto.randomBytes(length);
32
- return base32Encode(buffer);
33
- }
34
- function base32Encode(buffer) {
35
- const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
36
- let result = "";
37
- let bits = 0;
38
- let value = 0;
39
- for (let i = 0; i < buffer.length; i++) {
40
- value = value << 8 | buffer[i];
41
- bits += 8;
42
- while (bits >= 5) {
43
- result += alphabet[value >>> bits - 5 & 31];
44
- bits -= 5;
45
- }
46
- }
47
- if (bits > 0) {
48
- result += alphabet[value << 5 - bits & 31];
49
- }
50
- return result;
51
- }
52
- function base32Decode(encoded) {
53
- const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
54
- const lookup = /* @__PURE__ */ new Map();
55
- for (let i = 0; i < alphabet.length; i++) {
56
- lookup.set(alphabet[i], i);
57
- }
58
- const bytes = [];
59
- let bits = 0;
60
- let value = 0;
61
- for (const char of encoded.toUpperCase()) {
62
- if (char === "=") continue;
63
- const index = lookup.get(char);
64
- if (index === void 0) continue;
65
- value = value << 5 | index;
66
- bits += 5;
67
- if (bits >= 8) {
68
- bytes.push(value >>> bits - 8 & 255);
69
- bits -= 8;
70
- }
71
- }
72
- return Buffer.from(bytes);
73
- }
74
- function generateTOTP(secret, timeStep = 30) {
75
- const time = Math.floor(Date.now() / 1e3 / timeStep);
76
- const key = base32Decode(secret);
77
- const counter = Buffer.alloc(8);
78
- counter.writeBigUInt64BE(BigInt(time));
79
- const hmac = crypto.createHmac("sha1", key);
80
- hmac.update(counter);
81
- const hash = hmac.digest();
82
- const offset = hash[hash.length - 1] & 15;
83
- const code = ((hash[offset] & 127) << 24 | (hash[offset + 1] & 255) << 16 | (hash[offset + 2] & 255) << 8 | hash[offset + 3] & 255) % 1e6;
84
- return code.toString().padStart(6, "0");
85
- }
86
- function verifyTOTP(secret, code, timeStep = 30) {
87
- for (let i = -1; i <= 1; i++) {
88
- const time = Math.floor(Date.now() / 1e3 / timeStep) + i;
89
- const expectedCode = generateTOTPForTime(secret, time, timeStep);
90
- if (expectedCode === code) {
91
- return true;
92
- }
93
- }
94
- return false;
95
- }
96
- function generateTOTPForTime(secret, time, timeStep) {
97
- const key = base32Decode(secret);
98
- const counter = Buffer.alloc(8);
99
- counter.writeBigUInt64BE(BigInt(time));
100
- const hmac = crypto.createHmac("sha1", key);
101
- hmac.update(counter);
102
- const hash = hmac.digest();
103
- const offset = hash[hash.length - 1] & 15;
104
- const code = ((hash[offset] & 127) << 24 | (hash[offset + 1] & 255) << 16 | (hash[offset + 2] & 255) << 8 | hash[offset + 3] & 255) % 1e6;
105
- return code.toString().padStart(6, "0");
106
- }
107
- function generateOtpAuthUrl(secret, email, issuer = "SHIVA Code") {
108
- const encodedIssuer = encodeURIComponent(issuer);
109
- const encodedEmail = encodeURIComponent(email);
110
- return `otpauth://totp/${encodedIssuer}:${encodedEmail}?secret=${secret}&issuer=${encodedIssuer}&algorithm=SHA1&digits=6&period=30`;
111
- }
112
- function generateBackupCodes(count = 10) {
113
- const codes = [];
114
- for (let i = 0; i < count; i++) {
115
- const buffer = crypto.randomBytes(6);
116
- const code = buffer.toString("hex").toUpperCase();
117
- codes.push(`${code.slice(0, 3)}-${code.slice(3, 6)}-${code.slice(6, 9)}-${code.slice(9, 12)}`);
118
- }
119
- return codes;
120
- }
121
- function generateDeviceFingerprint() {
122
- const os = __require("os");
123
- const components = [
124
- os.hostname(),
125
- os.platform(),
126
- os.arch(),
127
- os.cpus()[0]?.model || "unknown",
128
- os.userInfo().username
129
- ];
130
- const fingerprint = crypto.createHash("sha256").update(components.join("|")).digest("hex").slice(0, 32);
131
- return fingerprint;
132
- }
133
- function getDeviceName() {
134
- const os = __require("os");
135
- const hostname = os.hostname();
136
- const platform = os.platform();
137
- const platformNames = {
138
- darwin: "macOS",
139
- linux: "Linux",
140
- win32: "Windows"
141
- };
142
- return `${hostname} (${platformNames[platform] || platform})`;
143
- }
144
- var TwoFactorService = class {
145
- /**
146
- * Start 2FA setup - generate secret and QR code
147
- */
148
- async startSetup(email) {
149
- try {
150
- const response = await api.setup2FA();
151
- if (!response.setup) {
152
- throw new Error(response.message || "2FA-Setup fehlgeschlagen");
153
- }
154
- return response.setup;
155
- } catch (error) {
156
- const secret = generateSecret();
157
- const otpAuthUrl = generateOtpAuthUrl(secret, email);
158
- const backupCodes = generateBackupCodes();
159
- return {
160
- secret,
161
- qrCodeDataUrl: otpAuthUrl,
162
- // In real implementation, would be a data URL
163
- backupCodes
164
- };
165
- }
166
- }
167
- /**
168
- * Verify TOTP code and complete setup
169
- */
170
- async verifySetup(code) {
171
- try {
172
- const response = await api.verify2FA(code);
173
- return response.success;
174
- } catch (error) {
175
- throw error;
176
- }
177
- }
178
- /**
179
- * Verify TOTP code during login
180
- */
181
- async verifyCode(code) {
182
- try {
183
- const response = await api.verify2FACode(code);
184
- return response.success;
185
- } catch (error) {
186
- throw error;
187
- }
188
- }
189
- /**
190
- * Verify backup code
191
- */
192
- async verifyBackupCode(code) {
193
- try {
194
- const response = await api.verifyBackupCode(code);
195
- return response.success;
196
- } catch (error) {
197
- throw error;
198
- }
199
- }
200
- /**
201
- * Get 2FA status
202
- */
203
- async getStatus() {
204
- try {
205
- const response = await api.get2FAStatus();
206
- return response;
207
- } catch (error) {
208
- return {
209
- enabled: false,
210
- backupCodesRemaining: 0
211
- };
212
- }
213
- }
214
- /**
215
- * Disable 2FA
216
- */
217
- async disable(code) {
218
- try {
219
- const response = await api.disable2FA(code);
220
- return response.success;
221
- } catch (error) {
222
- throw error;
223
- }
224
- }
225
- /**
226
- * Generate new backup codes
227
- */
228
- async regenerateBackupCodes() {
229
- try {
230
- const response = await api.regenerateBackupCodes();
231
- return response.codes;
232
- } catch (error) {
233
- throw error;
234
- }
235
- }
236
- /**
237
- * Verify TOTP code locally (for testing)
238
- */
239
- verifyLocal(secret, code) {
240
- return verifyTOTP(secret, code);
241
- }
242
- /**
243
- * Generate TOTP code locally (for testing)
244
- */
245
- generateLocal(secret) {
246
- return generateTOTP(secret);
247
- }
248
- };
249
- var DeviceTrustService = class {
250
- /**
251
- * Get current device fingerprint
252
- */
253
- getFingerprint() {
254
- return generateDeviceFingerprint();
255
- }
256
- /**
257
- * Get current device name
258
- */
259
- getDeviceName() {
260
- return getDeviceName();
261
- }
262
- /**
263
- * Register current device as trusted
264
- */
265
- async trustDevice(name) {
266
- const fingerprint = this.getFingerprint();
267
- const deviceName = name || this.getDeviceName();
268
- try {
269
- const response = await api.trustDevice({
270
- name: deviceName,
271
- fingerprint
272
- });
273
- return response.device;
274
- } catch (error) {
275
- throw error;
276
- }
277
- }
278
- /**
279
- * Check if current device is trusted
280
- */
281
- async isDeviceTrusted() {
282
- const fingerprint = this.getFingerprint();
283
- try {
284
- const devices = await api.getDevices();
285
- return devices.devices.some((d) => d.fingerprint === fingerprint);
286
- } catch (error) {
287
- return false;
288
- }
289
- }
290
- /**
291
- * List all trusted devices
292
- */
293
- async listDevices() {
294
- try {
295
- const response = await api.getDevices();
296
- const currentFingerprint = this.getFingerprint();
297
- return response.devices.map((d) => ({
298
- ...d,
299
- isCurrent: d.fingerprint === currentFingerprint
300
- }));
301
- } catch (error) {
302
- return [];
303
- }
304
- }
305
- /**
306
- * Revoke a trusted device
307
- */
308
- async revokeDevice(deviceId) {
309
- try {
310
- const response = await api.revokeDevice(deviceId);
311
- return response.success;
312
- } catch (error) {
313
- throw error;
314
- }
315
- }
316
- /**
317
- * Revoke all devices except current
318
- */
319
- async revokeAllDevices() {
320
- try {
321
- const response = await api.revokeAllDevices();
322
- return response.revokedCount;
323
- } catch (error) {
324
- throw error;
325
- }
326
- }
327
- };
328
- var twoFactorService = new TwoFactorService();
329
- var deviceTrustService = new DeviceTrustService();
330
- function displayQRCode(url) {
331
- log.newline();
332
- log.info("Scan diesen QR-Code mit deiner Authenticator App:");
333
- log.newline();
334
- log.dim("(QR-Code-Anzeige erfordert qrcode-terminal Paket)");
335
- log.newline();
336
- log.plain(`URL: ${url}`);
337
- log.newline();
338
- }
339
- function displayBackupCodes(codes) {
340
- log.newline();
341
- console.log(colors.orange.bold("Backup-Codes (speichern!)"));
342
- console.log(colors.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
343
- log.newline();
344
- for (let i = 0; i < codes.length; i++) {
345
- console.log(` ${i + 1}. ${codes[i]}`);
346
- }
347
- log.newline();
348
- log.warn("Diese Codes k\xF6nnen nur einmal verwendet werden!");
349
- log.dim("Speichere sie an einem sicheren Ort.");
350
- log.newline();
351
- }
352
- function displayDevices(devices) {
353
- log.newline();
354
- console.log(colors.orange.bold("Vertrauensw\xFCrdige Ger\xE4te"));
355
- console.log(colors.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
356
- log.newline();
357
- if (devices.length === 0) {
358
- log.dim("Keine Ger\xE4te registriert");
359
- return;
360
- }
361
- for (const device of devices) {
362
- const current = device.isCurrent ? colors.green(" (aktuell)") : "";
363
- console.log(` ${device.name}${current}`);
364
- log.dim(` ID: ${device.id}`);
365
- log.dim(` Zuletzt: ${formatRelativeTime(device.lastUsed)}`);
366
- if (device.expiresAt) {
367
- log.dim(` L\xE4uft ab: ${formatRelativeTime(device.expiresAt)}`);
368
- } else {
369
- log.dim(" L\xE4uft ab: nie");
370
- }
371
- log.newline();
372
- }
373
- }
374
- function formatRelativeTime(dateStr) {
375
- const date = new Date(dateStr);
376
- const now = /* @__PURE__ */ new Date();
377
- const diffMs = now.getTime() - date.getTime();
378
- const diffSec = Math.floor(diffMs / 1e3);
379
- const diffMin = Math.floor(diffSec / 60);
380
- const diffHour = Math.floor(diffMin / 60);
381
- const diffDay = Math.floor(diffHour / 24);
382
- if (diffSec < 60) return "gerade eben";
383
- if (diffMin < 60) return `vor ${diffMin} Minuten`;
384
- if (diffHour < 24) return `vor ${diffHour} Stunden`;
385
- if (diffDay < 30) return `vor ${diffDay} Tagen`;
386
- return date.toLocaleDateString("de-DE");
387
- }
388
-
389
- // src/services/auth/auth.ts
390
- var PORT_RANGE_START = 9876;
391
- var PORT_RANGE_END = 9886;
392
- async function loginWithOtp(email) {
393
- try {
394
- log.info(`Sende Login-Code an ${email}...`);
395
- const otpResponse = await api.requestOtp(email);
396
- if (!otpResponse.success) {
397
- log.error(otpResponse.message || "Fehler beim Senden des Codes");
398
- return false;
399
- }
400
- log.success("Code wurde gesendet!");
401
- log.dim(`Verbleibende Versuche: ${otpResponse.remainingAttempts}`);
402
- log.newline();
403
- const { otp } = await inquirer.prompt([
404
- {
405
- type: "input",
406
- name: "otp",
407
- message: "Code eingeben:",
408
- validate: (input) => {
409
- if (!/^\d{6}$/.test(input)) {
410
- return "Bitte gib den 6-stelligen Code ein";
411
- }
412
- return true;
413
- }
414
- }
415
- ]);
416
- const authResponse = await api.verifyOtp(otpResponse.token, otp);
417
- if (authResponse.requires2FA && authResponse.tempToken) {
418
- log.newline();
419
- log.info("2FA ist aktiviert. Bitte verifiziere dich.");
420
- log.newline();
421
- const result = await handle2FALoginFlow(authResponse.tempToken, authResponse.email || email);
422
- return result;
423
- }
424
- if (!authResponse.success || !authResponse.token || !authResponse.user) {
425
- log.error(authResponse.message || "Ung\xFCltiger Code");
426
- return false;
427
- }
428
- setAuth(authResponse.token, authResponse.user);
429
- log.newline();
430
- log.success(`Angemeldet als ${authResponse.user.email} (${authResponse.user.tier.toUpperCase()})`);
431
- return true;
432
- } catch (error) {
433
- const message = error instanceof Error ? error.message : "Unbekannter Fehler";
434
- log.error(`Login fehlgeschlagen: ${message}`);
435
- return false;
436
- }
437
- }
438
- async function handle2FALoginFlow(tempToken, email) {
439
- const MAX_ATTEMPTS = 3;
440
- for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
441
- const { code } = await inquirer.prompt([{
442
- type: "input",
443
- name: "code",
444
- message: "2FA-Code (6 Ziffern oder Backup-Code):",
445
- validate: (input) => {
446
- if (/^\d{6}$/.test(input) || /^[A-Za-z0-9-]+$/.test(input)) {
447
- return true;
448
- }
449
- return "Bitte gib einen g\xFCltigen Code ein";
450
- }
451
- }]);
452
- const { rememberDevice } = await inquirer.prompt([{
453
- type: "confirm",
454
- name: "rememberDevice",
455
- message: "Diesem Ger\xE4t vertrauen?",
456
- default: true
457
- }]);
458
- try {
459
- const result = await api.validate2FALogin({
460
- tempToken,
461
- code,
462
- rememberDevice
463
- });
464
- if (result.success && result.token && result.user) {
465
- setAuth(result.token, result.user);
466
- if (result.method === "backup" && result.backupCodesRemaining !== void 0) {
467
- log.warn(`Backup-Code verwendet. Noch ${result.backupCodesRemaining} Codes \xFCbrig.`);
468
- }
469
- log.newline();
470
- log.success(`Angemeldet als ${result.user.email} (${result.user.tier.toUpperCase()})`);
471
- return true;
472
- }
473
- if (attempt < MAX_ATTEMPTS) {
474
- log.error(`Ung\xFCltiger Code. Noch ${MAX_ATTEMPTS - attempt} Versuche.`);
475
- }
476
- } catch (error) {
477
- const message = error instanceof Error ? error.message : "Verifizierung fehlgeschlagen";
478
- if (message.includes("expired") || message.includes("abgelaufen")) {
479
- log.error("2FA-Token abgelaufen. Bitte erneut anmelden.");
480
- return false;
481
- }
482
- if (attempt < MAX_ATTEMPTS) {
483
- log.error(`${message}. Noch ${MAX_ATTEMPTS - attempt} Versuche.`);
484
- }
485
- }
486
- }
487
- log.error("2FA-Verifizierung fehlgeschlagen");
488
- return false;
489
- }
490
- async function findAvailablePort() {
491
- for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
492
- const isAvailable = await checkPort(port);
493
- if (isAvailable) {
494
- return port;
495
- }
496
- }
497
- throw new Error("Kein freier Port f\xFCr Callback-Server gefunden");
498
- }
499
- function checkPort(port) {
500
- return new Promise((resolve) => {
501
- const server = http.createServer();
502
- server.once("error", () => resolve(false));
503
- server.once("listening", () => {
504
- server.close();
505
- resolve(true);
506
- });
507
- server.listen(port, "127.0.0.1");
508
- });
509
- }
510
- function getCallbackHtml(type, data) {
511
- const baseStyles = `
512
- * { margin: 0; padding: 0; box-sizing: border-box; }
513
- body {
514
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
515
- min-height: 100vh;
516
- display: flex;
517
- align-items: center;
518
- justify-content: center;
519
- background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #16213e 100%);
520
- color: #fff;
521
- }
522
- .container {
523
- text-align: center;
524
- padding: 3rem;
525
- max-width: 420px;
526
- }
527
- .logo {
528
- width: 80px;
529
- height: 80px;
530
- margin: 0 auto 1.5rem;
531
- background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
532
- border-radius: 20px;
533
- display: flex;
534
- align-items: center;
535
- justify-content: center;
536
- box-shadow: 0 10px 40px rgba(249, 115, 22, 0.3);
537
- }
538
- .logo svg {
539
- width: 48px;
540
- height: 48px;
541
- }
542
- h1 {
543
- font-size: 1.75rem;
544
- font-weight: 700;
545
- margin-bottom: 0.75rem;
546
- }
547
- .subtitle {
548
- color: #9ca3af;
549
- font-size: 1rem;
550
- line-height: 1.5;
551
- }
552
- .email {
553
- color: #f97316;
554
- font-weight: 600;
555
- }
556
- .status-icon {
557
- width: 64px;
558
- height: 64px;
559
- margin: 0 auto 1.5rem;
560
- border-radius: 50%;
561
- display: flex;
562
- align-items: center;
563
- justify-content: center;
564
- }
565
- .status-icon.success {
566
- background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
567
- box-shadow: 0 10px 40px rgba(34, 197, 94, 0.3);
568
- }
569
- .status-icon.error {
570
- background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
571
- box-shadow: 0 10px 40px rgba(239, 68, 68, 0.3);
572
- }
573
- .status-icon svg {
574
- width: 32px;
575
- height: 32px;
576
- }
577
- .spinner {
578
- width: 48px;
579
- height: 48px;
580
- margin: 0 auto 1.5rem;
581
- border: 3px solid rgba(249, 115, 22, 0.2);
582
- border-top-color: #f97316;
583
- border-radius: 50%;
584
- animation: spin 1s linear infinite;
585
- }
586
- @keyframes spin {
587
- to { transform: rotate(360deg); }
588
- }
589
- .hint {
590
- margin-top: 1.5rem;
591
- padding-top: 1.5rem;
592
- border-top: 1px solid rgba(255, 255, 255, 0.1);
593
- color: #6b7280;
594
- font-size: 0.875rem;
595
- }
596
- `;
597
- const shivaLogo = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>`;
598
- const checkIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
599
- const xIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
600
- if (type === "success") {
601
- return `<!DOCTYPE html>
602
- <html lang="de">
603
- <head>
604
- <meta charset="UTF-8">
605
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
606
- <title>SHIVA CLI - Angemeldet</title>
607
- <style>${baseStyles}</style>
608
- </head>
609
- <body>
610
- <div class="container">
611
- <div class="status-icon success">${checkIcon}</div>
612
- <h1>Erfolgreich angemeldet!</h1>
613
- <p class="subtitle">Willkommen zur\xFCck, <span class="email">${data.email}</span></p>
614
- <p class="hint">Du kannst dieses Fenster jetzt schlie\xDFen und zum Terminal zur\xFCckkehren.</p>
615
- </div>
616
- <script>setTimeout(() => window.close(), 3000);</script>
617
- </body>
618
- </html>`;
619
- }
620
- if (type === "error") {
621
- return `<!DOCTYPE html>
622
- <html lang="de">
623
- <head>
624
- <meta charset="UTF-8">
625
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
626
- <title>SHIVA CLI - Fehler</title>
627
- <style>${baseStyles}</style>
628
- </head>
629
- <body>
630
- <div class="container">
631
- <div class="status-icon error">${xIcon}</div>
632
- <h1>Anmeldung fehlgeschlagen</h1>
633
- <p class="subtitle">${data.message || "Ein unbekannter Fehler ist aufgetreten."}</p>
634
- <p class="hint">Du kannst dieses Fenster schlie\xDFen und es erneut versuchen.</p>
635
- </div>
636
- </body>
637
- </html>`;
638
- }
639
- return `<!DOCTYPE html>
640
- <html lang="de">
641
- <head>
642
- <meta charset="UTF-8">
643
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
644
- <title>SHIVA CLI</title>
645
- <style>${baseStyles}</style>
646
- </head>
647
- <body>
648
- <div class="container">
649
- <div class="logo">${shivaLogo}</div>
650
- <div class="spinner"></div>
651
- <h1>SHIVA CLI</h1>
652
- <p class="subtitle">Warte auf Login-Callback...</p>
653
- </div>
654
- </body>
655
- </html>`;
656
- }
657
- function startCallbackServer(port) {
658
- return new Promise((resolve, reject) => {
659
- const server = http.createServer((req, res) => {
660
- const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
661
- if (url.pathname === "/callback") {
662
- const token = url.searchParams.get("token");
663
- const userId = url.searchParams.get("userId");
664
- const email = url.searchParams.get("email");
665
- const tier = url.searchParams.get("tier");
666
- const error = url.searchParams.get("error");
667
- if (error) {
668
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
669
- res.end(getCallbackHtml("error", { message: error }));
670
- server.close();
671
- reject(new Error(error));
672
- return;
673
- }
674
- if (!token || !userId || !email) {
675
- res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
676
- res.end(getCallbackHtml("error", { message: "Token oder Benutzer-Daten fehlen." }));
677
- server.close();
678
- reject(new Error("Ung\xFCltige Callback-Parameter"));
679
- return;
680
- }
681
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
682
- res.end(getCallbackHtml("success", { email }));
683
- setTimeout(() => {
684
- server.close();
685
- resolve({
686
- token,
687
- user: {
688
- id: parseInt(userId, 10),
689
- email,
690
- tier: tier || "free"
691
- }
692
- });
693
- }, 100);
694
- return;
695
- }
696
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
697
- res.end(getCallbackHtml("waiting", {}));
698
- });
699
- const timeout = setTimeout(() => {
700
- server.close();
701
- reject(new Error("Login-Timeout - keine Antwort erhalten"));
702
- }, 3e5);
703
- server.once("close", () => {
704
- clearTimeout(timeout);
705
- });
706
- server.once("error", (err) => {
707
- clearTimeout(timeout);
708
- reject(err);
709
- });
710
- server.listen(port, "127.0.0.1");
711
- });
712
- }
713
- async function loginWithBrowser() {
714
- const config = getConfig();
715
- try {
716
- const port = await findAvailablePort();
717
- const callbackUrl = `http://127.0.0.1:${port}/callback`;
718
- const baseUrl = config.apiEndpoint.replace("/api", "");
719
- const loginUrl = `${baseUrl}/auth/cli-login?callback=${encodeURIComponent(callbackUrl)}`;
720
- const serverPromise = startCallbackServer(port);
721
- log.newline();
722
- log.info("\xD6ffne Browser zur Anmeldung...");
723
- try {
724
- await open(loginUrl);
725
- } catch {
726
- }
727
- log.newline();
728
- log.plain("Bitte \xF6ffne diese URL in deinem Browser um die Anmeldung abzuschlie\xDFen:");
729
- log.newline();
730
- console.log(` ${colors.cyan(loginUrl)}`);
731
- log.newline();
732
- log.dim("Warte auf Anmeldung... (Timeout: 5 Minuten)");
733
- log.newline();
734
- const result = await serverPromise;
735
- setAuth(result.token, result.user);
736
- try {
737
- const tfaStatus = await twoFactorService.getStatus();
738
- if (tfaStatus.enabled) {
739
- log.newline();
740
- log.info("2FA ist aktiviert. Bitte verifiziere dich.");
741
- log.newline();
742
- const isDeviceTrusted = await deviceTrustService.isDeviceTrusted();
743
- if (!isDeviceTrusted) {
744
- const verified = await prompt2FAVerification();
745
- if (!verified) {
746
- clearAuth();
747
- log.error("2FA-Verifizierung fehlgeschlagen");
748
- return false;
749
- }
750
- const { trustDevice } = await inquirer.prompt([{
751
- type: "confirm",
752
- name: "trustDevice",
753
- message: "Diesem Ger\xE4t vertrauen?",
754
- default: true
755
- }]);
756
- if (trustDevice) {
757
- try {
758
- await deviceTrustService.trustDevice();
759
- log.success("Ger\xE4t als vertrauensw\xFCrdig markiert");
760
- } catch {
761
- }
762
- }
763
- } else {
764
- log.dim("Ger\xE4t ist vertrauensw\xFCrdig - 2FA \xFCbersprungen");
765
- }
766
- }
767
- } catch {
768
- }
769
- log.success(`Angemeldet als ${result.user.email} (${result.user.tier.toUpperCase()})`);
770
- return true;
771
- } catch (error) {
772
- const message = error instanceof Error ? error.message : "Unbekannter Fehler";
773
- log.error(`Login fehlgeschlagen: ${message}`);
774
- log.newline();
775
- log.info("Fallback: Manueller Token-Eintrag");
776
- const loginUrl = `${config.apiEndpoint.replace("/api", "")}/auth/cli-login`;
777
- log.dim(`URL: ${loginUrl}`);
778
- log.newline();
779
- const { useManual } = await inquirer.prompt([{
780
- type: "confirm",
781
- name: "useManual",
782
- message: "Token manuell eingeben?",
783
- default: true
784
- }]);
785
- if (!useManual) {
786
- return false;
787
- }
788
- try {
789
- await open(loginUrl);
790
- } catch {
791
- log.dim("Browser konnte nicht ge\xF6ffnet werden");
792
- log.dim(`\xD6ffne manuell: ${loginUrl}`);
793
- }
794
- const { token } = await inquirer.prompt([{
795
- type: "password",
796
- name: "token",
797
- message: "Token:",
798
- mask: "*"
799
- }]);
800
- if (!token) {
801
- log.error("Kein Token eingegeben");
802
- return false;
803
- }
804
- setAuth(token, { id: 0, email: "", tier: "free" });
805
- try {
806
- const response = await api.getCurrentUser();
807
- setAuth(token, response.user);
808
- log.success(`Angemeldet als ${response.user.email}`);
809
- return true;
810
- } catch {
811
- clearAuth();
812
- log.error("Ung\xFCltiger Token");
813
- return false;
814
- }
815
- }
816
- }
817
- function logout() {
818
- clearAuth();
819
- log.success("Abgemeldet");
820
- }
821
- async function prompt2FAVerification() {
822
- const MAX_ATTEMPTS = 3;
823
- for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
824
- const { method } = await inquirer.prompt([{
825
- type: "list",
826
- name: "method",
827
- message: "Verifizierungsmethode:",
828
- choices: [
829
- { name: "Authenticator App Code", value: "totp" },
830
- { name: "Backup Code", value: "backup" },
831
- { name: "Abbrechen", value: "cancel" }
832
- ]
833
- }]);
834
- if (method === "cancel") {
835
- return false;
836
- }
837
- const promptMessage = method === "totp" ? "6-stelliger Code aus deiner Authenticator App:" : "Backup-Code (Format: XXX-XXX-XXX-XXX):";
838
- const { code } = await inquirer.prompt([{
839
- type: "input",
840
- name: "code",
841
- message: promptMessage,
842
- validate: (input) => {
843
- if (method === "totp") {
844
- return /^\d{6}$/.test(input) || "Bitte gib einen 6-stelligen Code ein";
845
- }
846
- return input.length > 0 || "Bitte gib einen Backup-Code ein";
847
- }
848
- }]);
849
- try {
850
- let verified = false;
851
- if (method === "totp") {
852
- verified = await twoFactorService.verifyCode(code);
853
- } else {
854
- verified = await twoFactorService.verifyBackupCode(code);
855
- }
856
- if (verified) {
857
- log.success("2FA-Verifizierung erfolgreich");
858
- return true;
859
- }
860
- } catch (error) {
861
- }
862
- if (attempt < MAX_ATTEMPTS) {
863
- log.error(`Ung\xFCltiger Code. Noch ${MAX_ATTEMPTS - attempt} Versuche.`);
864
- }
865
- }
866
- return false;
867
- }
868
-
869
22
  // src/i18n/locales/de.json
870
23
  var de_default = {
871
24
  common: {
@@ -928,7 +81,20 @@ var de_default = {
928
81
  invalidEmail: "Bitte gib eine gultige Email-Adresse ein",
929
82
  twoFactorEnabled: "2FA aktiviert.",
930
83
  twoFactorDisabled: "2FA deaktiviert.",
931
- invalidToken: "Ungultiger Token."
84
+ invalidToken: "Ungultiger Token.",
85
+ callback: {
86
+ successTitle: "Erfolgreich angemeldet!",
87
+ successSubtitle: "Willkommen zur\xFCck,",
88
+ successHint: "Du kannst dieses Fenster jetzt schlie\xDFen und zum Terminal zur\xFCckkehren.",
89
+ errorTitle: "Anmeldung fehlgeschlagen",
90
+ errorDefault: "Ein unbekannter Fehler ist aufgetreten.",
91
+ errorHint: "Du kannst dieses Fenster schlie\xDFen und es erneut versuchen.",
92
+ waitingTitle: "Authentifizierung",
93
+ waitingSubtitle: "Warte auf Login-Best\xE4tigung...",
94
+ pageTitle: "SHIVA Code",
95
+ pageTitleSuccess: "SHIVA Code - Angemeldet",
96
+ pageTitleError: "SHIVA Code - Fehler"
97
+ }
932
98
  },
933
99
  errors: {
934
100
  unknown: "Ein unbekannter Fehler ist aufgetreten",
@@ -2977,7 +2143,20 @@ var en_default = {
2977
2143
  invalidEmail: "Please enter a valid email address",
2978
2144
  twoFactorEnabled: "2FA enabled.",
2979
2145
  twoFactorDisabled: "2FA disabled.",
2980
- invalidToken: "Invalid token."
2146
+ invalidToken: "Invalid token.",
2147
+ callback: {
2148
+ successTitle: "Successfully logged in!",
2149
+ successSubtitle: "Welcome back,",
2150
+ successHint: "You can now close this window and return to the terminal.",
2151
+ errorTitle: "Login failed",
2152
+ errorDefault: "An unknown error occurred.",
2153
+ errorHint: "You can close this window and try again.",
2154
+ waitingTitle: "Authentication",
2155
+ waitingSubtitle: "Waiting for login confirmation...",
2156
+ pageTitle: "SHIVA Code",
2157
+ pageTitleSuccess: "SHIVA Code - Logged in",
2158
+ pageTitleError: "SHIVA Code - Error"
2159
+ }
2981
2160
  },
2982
2161
  errors: {
2983
2162
  unknown: "An unknown error occurred",
@@ -4656,7 +3835,20 @@ var fr_default = {
4656
3835
  invalidEmail: "Veuillez entrer une adresse email valide",
4657
3836
  twoFactorEnabled: "2FA active.",
4658
3837
  twoFactorDisabled: "2FA desactive.",
4659
- invalidToken: "Token invalide."
3838
+ invalidToken: "Token invalide.",
3839
+ callback: {
3840
+ successTitle: "Connexion r\xE9ussie!",
3841
+ successSubtitle: "Bienvenue,",
3842
+ successHint: "Vous pouvez maintenant fermer cette fen\xEAtre et retourner au terminal.",
3843
+ errorTitle: "\xC9chec de la connexion",
3844
+ errorDefault: "Une erreur inconnue s'est produite.",
3845
+ errorHint: "Vous pouvez fermer cette fen\xEAtre et r\xE9essayer.",
3846
+ waitingTitle: "Authentification",
3847
+ waitingSubtitle: "En attente de la confirmation...",
3848
+ pageTitle: "SHIVA Code",
3849
+ pageTitleSuccess: "SHIVA Code - Connect\xE9",
3850
+ pageTitleError: "SHIVA Code - Erreur"
3851
+ }
4660
3852
  },
4661
3853
  errors: {
4662
3854
  unknown: "Une erreur inconnue s'est produite",
@@ -6284,7 +5476,20 @@ var es_default = {
6284
5476
  invalidEmail: "Por favor introduce una direccion de email valida",
6285
5477
  twoFactorEnabled: "2FA habilitado.",
6286
5478
  twoFactorDisabled: "2FA deshabilitado.",
6287
- invalidToken: "Token invalido."
5479
+ invalidToken: "Token invalido.",
5480
+ callback: {
5481
+ successTitle: "\xA1Sesi\xF3n iniciada con \xE9xito!",
5482
+ successSubtitle: "Bienvenido de nuevo,",
5483
+ successHint: "Ahora puedes cerrar esta ventana y volver al terminal.",
5484
+ errorTitle: "Error de inicio de sesi\xF3n",
5485
+ errorDefault: "Ha ocurrido un error desconocido.",
5486
+ errorHint: "Puedes cerrar esta ventana e intentarlo de nuevo.",
5487
+ waitingTitle: "Autenticaci\xF3n",
5488
+ waitingSubtitle: "Esperando confirmaci\xF3n de inicio de sesi\xF3n...",
5489
+ pageTitle: "SHIVA Code",
5490
+ pageTitleSuccess: "SHIVA Code - Conectado",
5491
+ pageTitleError: "SHIVA Code - Error"
5492
+ }
6288
5493
  },
6289
5494
  errors: {
6290
5495
  unknown: "Ha ocurrido un error desconocido",
@@ -8227,94 +7432,1004 @@ var es_default = {
8227
7432
  deletedInfo: "Todos los datos del proyecto han sido eliminados de la nube."
8228
7433
  }
8229
7434
  };
8230
-
8231
- // src/i18n/types.ts
8232
- var SUPPORTED_LANGUAGES = ["de", "en", "fr", "es"];
8233
- var LANGUAGE_NAMES = {
8234
- de: "Deutsch",
8235
- en: "English",
8236
- fr: "Francais",
8237
- es: "Espanol"
7435
+
7436
+ // src/i18n/types.ts
7437
+ var SUPPORTED_LANGUAGES = ["de", "en", "fr", "es"];
7438
+ var LANGUAGE_NAMES = {
7439
+ de: "Deutsch",
7440
+ en: "English",
7441
+ fr: "Francais",
7442
+ es: "Espanol"
7443
+ };
7444
+
7445
+ // src/i18n/index.ts
7446
+ var translations = {
7447
+ de: de_default,
7448
+ en: en_default,
7449
+ fr: fr_default,
7450
+ es: es_default
7451
+ };
7452
+ var currentLanguage = "de";
7453
+ function setLanguage(lang) {
7454
+ currentLanguage = lang;
7455
+ }
7456
+ function getLanguage() {
7457
+ return currentLanguage;
7458
+ }
7459
+ function isValidLanguage(lang) {
7460
+ return ["de", "en", "fr", "es"].includes(lang);
7461
+ }
7462
+ function t(key, params) {
7463
+ const keys = key.split(".");
7464
+ let value = translations[currentLanguage];
7465
+ for (const k of keys) {
7466
+ if (value && typeof value === "object" && k in value) {
7467
+ value = value[k];
7468
+ } else {
7469
+ value = void 0;
7470
+ break;
7471
+ }
7472
+ }
7473
+ if (typeof value !== "string") {
7474
+ value = keys.reduce(
7475
+ (obj, k) => obj && typeof obj === "object" && k in obj ? obj[k] : void 0,
7476
+ translations.de
7477
+ );
7478
+ }
7479
+ if (typeof value !== "string") {
7480
+ return key;
7481
+ }
7482
+ if (params) {
7483
+ for (const [param, val] of Object.entries(params)) {
7484
+ value = value.replace(new RegExp(`\\{${param}\\}`, "g"), String(val));
7485
+ }
7486
+ }
7487
+ return value;
7488
+ }
7489
+ function tArray(key) {
7490
+ const keys = key.split(".");
7491
+ let value = translations[currentLanguage];
7492
+ for (const k of keys) {
7493
+ if (value && typeof value === "object" && k in value) {
7494
+ value = value[k];
7495
+ } else {
7496
+ value = void 0;
7497
+ break;
7498
+ }
7499
+ }
7500
+ if (!Array.isArray(value)) {
7501
+ value = keys.reduce(
7502
+ (obj, k) => obj && typeof obj === "object" && k in obj ? obj[k] : void 0,
7503
+ translations.de
7504
+ );
7505
+ }
7506
+ return Array.isArray(value) ? value : [];
7507
+ }
7508
+ function initializeLanguage(storedLang) {
7509
+ if (storedLang && isValidLanguage(storedLang)) {
7510
+ setLanguage(storedLang);
7511
+ return;
7512
+ }
7513
+ try {
7514
+ const systemLocale = Intl.DateTimeFormat().resolvedOptions().locale;
7515
+ const langCode = systemLocale.split("-")[0].toLowerCase();
7516
+ if (isValidLanguage(langCode)) {
7517
+ setLanguage(langCode);
7518
+ return;
7519
+ }
7520
+ } catch {
7521
+ }
7522
+ setLanguage("de");
7523
+ }
7524
+
7525
+ // src/services/auth/auth.ts
7526
+ import inquirer from "inquirer";
7527
+ import open from "open";
7528
+ import http from "http";
7529
+ import { URL } from "url";
7530
+
7531
+ // src/services/auth/two-factor.ts
7532
+ import * as crypto from "crypto";
7533
+ function generateSecret(length = 20) {
7534
+ const buffer = crypto.randomBytes(length);
7535
+ return base32Encode(buffer);
7536
+ }
7537
+ function base32Encode(buffer) {
7538
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
7539
+ let result = "";
7540
+ let bits = 0;
7541
+ let value = 0;
7542
+ for (let i = 0; i < buffer.length; i++) {
7543
+ value = value << 8 | buffer[i];
7544
+ bits += 8;
7545
+ while (bits >= 5) {
7546
+ result += alphabet[value >>> bits - 5 & 31];
7547
+ bits -= 5;
7548
+ }
7549
+ }
7550
+ if (bits > 0) {
7551
+ result += alphabet[value << 5 - bits & 31];
7552
+ }
7553
+ return result;
7554
+ }
7555
+ function base32Decode(encoded) {
7556
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
7557
+ const lookup = /* @__PURE__ */ new Map();
7558
+ for (let i = 0; i < alphabet.length; i++) {
7559
+ lookup.set(alphabet[i], i);
7560
+ }
7561
+ const bytes = [];
7562
+ let bits = 0;
7563
+ let value = 0;
7564
+ for (const char of encoded.toUpperCase()) {
7565
+ if (char === "=") continue;
7566
+ const index = lookup.get(char);
7567
+ if (index === void 0) continue;
7568
+ value = value << 5 | index;
7569
+ bits += 5;
7570
+ if (bits >= 8) {
7571
+ bytes.push(value >>> bits - 8 & 255);
7572
+ bits -= 8;
7573
+ }
7574
+ }
7575
+ return Buffer.from(bytes);
7576
+ }
7577
+ function generateTOTP(secret, timeStep = 30) {
7578
+ const time = Math.floor(Date.now() / 1e3 / timeStep);
7579
+ const key = base32Decode(secret);
7580
+ const counter = Buffer.alloc(8);
7581
+ counter.writeBigUInt64BE(BigInt(time));
7582
+ const hmac = crypto.createHmac("sha1", key);
7583
+ hmac.update(counter);
7584
+ const hash = hmac.digest();
7585
+ const offset = hash[hash.length - 1] & 15;
7586
+ const code = ((hash[offset] & 127) << 24 | (hash[offset + 1] & 255) << 16 | (hash[offset + 2] & 255) << 8 | hash[offset + 3] & 255) % 1e6;
7587
+ return code.toString().padStart(6, "0");
7588
+ }
7589
+ function verifyTOTP(secret, code, timeStep = 30) {
7590
+ for (let i = -1; i <= 1; i++) {
7591
+ const time = Math.floor(Date.now() / 1e3 / timeStep) + i;
7592
+ const expectedCode = generateTOTPForTime(secret, time, timeStep);
7593
+ if (expectedCode === code) {
7594
+ return true;
7595
+ }
7596
+ }
7597
+ return false;
7598
+ }
7599
+ function generateTOTPForTime(secret, time, timeStep) {
7600
+ const key = base32Decode(secret);
7601
+ const counter = Buffer.alloc(8);
7602
+ counter.writeBigUInt64BE(BigInt(time));
7603
+ const hmac = crypto.createHmac("sha1", key);
7604
+ hmac.update(counter);
7605
+ const hash = hmac.digest();
7606
+ const offset = hash[hash.length - 1] & 15;
7607
+ const code = ((hash[offset] & 127) << 24 | (hash[offset + 1] & 255) << 16 | (hash[offset + 2] & 255) << 8 | hash[offset + 3] & 255) % 1e6;
7608
+ return code.toString().padStart(6, "0");
7609
+ }
7610
+ function generateOtpAuthUrl(secret, email, issuer = "SHIVA Code") {
7611
+ const encodedIssuer = encodeURIComponent(issuer);
7612
+ const encodedEmail = encodeURIComponent(email);
7613
+ return `otpauth://totp/${encodedIssuer}:${encodedEmail}?secret=${secret}&issuer=${encodedIssuer}&algorithm=SHA1&digits=6&period=30`;
7614
+ }
7615
+ function generateBackupCodes(count = 10) {
7616
+ const codes = [];
7617
+ for (let i = 0; i < count; i++) {
7618
+ const buffer = crypto.randomBytes(6);
7619
+ const code = buffer.toString("hex").toUpperCase();
7620
+ codes.push(`${code.slice(0, 3)}-${code.slice(3, 6)}-${code.slice(6, 9)}-${code.slice(9, 12)}`);
7621
+ }
7622
+ return codes;
7623
+ }
7624
+ function generateDeviceFingerprint() {
7625
+ const os = __require("os");
7626
+ const components = [
7627
+ os.hostname(),
7628
+ os.platform(),
7629
+ os.arch(),
7630
+ os.cpus()[0]?.model || "unknown",
7631
+ os.userInfo().username
7632
+ ];
7633
+ const fingerprint = crypto.createHash("sha256").update(components.join("|")).digest("hex").slice(0, 32);
7634
+ return fingerprint;
7635
+ }
7636
+ function getDeviceName() {
7637
+ const os = __require("os");
7638
+ const hostname = os.hostname();
7639
+ const platform = os.platform();
7640
+ const platformNames = {
7641
+ darwin: "macOS",
7642
+ linux: "Linux",
7643
+ win32: "Windows"
7644
+ };
7645
+ return `${hostname} (${platformNames[platform] || platform})`;
7646
+ }
7647
+ var TwoFactorService = class {
7648
+ /**
7649
+ * Start 2FA setup - generate secret and QR code
7650
+ */
7651
+ async startSetup(email) {
7652
+ try {
7653
+ const response = await api.setup2FA();
7654
+ if (!response.setup) {
7655
+ throw new Error(response.message || "2FA-Setup fehlgeschlagen");
7656
+ }
7657
+ return response.setup;
7658
+ } catch (error) {
7659
+ const secret = generateSecret();
7660
+ const otpAuthUrl = generateOtpAuthUrl(secret, email);
7661
+ const backupCodes = generateBackupCodes();
7662
+ return {
7663
+ secret,
7664
+ qrCodeDataUrl: otpAuthUrl,
7665
+ // In real implementation, would be a data URL
7666
+ backupCodes
7667
+ };
7668
+ }
7669
+ }
7670
+ /**
7671
+ * Verify TOTP code and complete setup
7672
+ */
7673
+ async verifySetup(code) {
7674
+ try {
7675
+ const response = await api.verify2FA(code);
7676
+ return response.success;
7677
+ } catch (error) {
7678
+ throw error;
7679
+ }
7680
+ }
7681
+ /**
7682
+ * Verify TOTP code during login
7683
+ */
7684
+ async verifyCode(code) {
7685
+ try {
7686
+ const response = await api.verify2FACode(code);
7687
+ return response.success;
7688
+ } catch (error) {
7689
+ throw error;
7690
+ }
7691
+ }
7692
+ /**
7693
+ * Verify backup code
7694
+ */
7695
+ async verifyBackupCode(code) {
7696
+ try {
7697
+ const response = await api.verifyBackupCode(code);
7698
+ return response.success;
7699
+ } catch (error) {
7700
+ throw error;
7701
+ }
7702
+ }
7703
+ /**
7704
+ * Get 2FA status
7705
+ */
7706
+ async getStatus() {
7707
+ try {
7708
+ const response = await api.get2FAStatus();
7709
+ return response;
7710
+ } catch (error) {
7711
+ return {
7712
+ enabled: false,
7713
+ backupCodesRemaining: 0
7714
+ };
7715
+ }
7716
+ }
7717
+ /**
7718
+ * Disable 2FA
7719
+ */
7720
+ async disable(code) {
7721
+ try {
7722
+ const response = await api.disable2FA(code);
7723
+ return response.success;
7724
+ } catch (error) {
7725
+ throw error;
7726
+ }
7727
+ }
7728
+ /**
7729
+ * Generate new backup codes
7730
+ */
7731
+ async regenerateBackupCodes() {
7732
+ try {
7733
+ const response = await api.regenerateBackupCodes();
7734
+ return response.codes;
7735
+ } catch (error) {
7736
+ throw error;
7737
+ }
7738
+ }
7739
+ /**
7740
+ * Verify TOTP code locally (for testing)
7741
+ */
7742
+ verifyLocal(secret, code) {
7743
+ return verifyTOTP(secret, code);
7744
+ }
7745
+ /**
7746
+ * Generate TOTP code locally (for testing)
7747
+ */
7748
+ generateLocal(secret) {
7749
+ return generateTOTP(secret);
7750
+ }
7751
+ };
7752
+ var DeviceTrustService = class {
7753
+ /**
7754
+ * Get current device fingerprint
7755
+ */
7756
+ getFingerprint() {
7757
+ return generateDeviceFingerprint();
7758
+ }
7759
+ /**
7760
+ * Get current device name
7761
+ */
7762
+ getDeviceName() {
7763
+ return getDeviceName();
7764
+ }
7765
+ /**
7766
+ * Register current device as trusted
7767
+ */
7768
+ async trustDevice(name) {
7769
+ const fingerprint = this.getFingerprint();
7770
+ const deviceName = name || this.getDeviceName();
7771
+ try {
7772
+ const response = await api.trustDevice({
7773
+ name: deviceName,
7774
+ fingerprint
7775
+ });
7776
+ return response.device;
7777
+ } catch (error) {
7778
+ throw error;
7779
+ }
7780
+ }
7781
+ /**
7782
+ * Check if current device is trusted
7783
+ */
7784
+ async isDeviceTrusted() {
7785
+ const fingerprint = this.getFingerprint();
7786
+ try {
7787
+ const devices = await api.getDevices();
7788
+ return devices.devices.some((d) => d.fingerprint === fingerprint);
7789
+ } catch (error) {
7790
+ return false;
7791
+ }
7792
+ }
7793
+ /**
7794
+ * List all trusted devices
7795
+ */
7796
+ async listDevices() {
7797
+ try {
7798
+ const response = await api.getDevices();
7799
+ const currentFingerprint = this.getFingerprint();
7800
+ return response.devices.map((d) => ({
7801
+ ...d,
7802
+ isCurrent: d.fingerprint === currentFingerprint
7803
+ }));
7804
+ } catch (error) {
7805
+ return [];
7806
+ }
7807
+ }
7808
+ /**
7809
+ * Revoke a trusted device
7810
+ */
7811
+ async revokeDevice(deviceId) {
7812
+ try {
7813
+ const response = await api.revokeDevice(deviceId);
7814
+ return response.success;
7815
+ } catch (error) {
7816
+ throw error;
7817
+ }
7818
+ }
7819
+ /**
7820
+ * Revoke all devices except current
7821
+ */
7822
+ async revokeAllDevices() {
7823
+ try {
7824
+ const response = await api.revokeAllDevices();
7825
+ return response.revokedCount;
7826
+ } catch (error) {
7827
+ throw error;
7828
+ }
7829
+ }
8238
7830
  };
7831
+ var twoFactorService = new TwoFactorService();
7832
+ var deviceTrustService = new DeviceTrustService();
7833
+ function displayQRCode(url) {
7834
+ log.newline();
7835
+ log.info("Scan diesen QR-Code mit deiner Authenticator App:");
7836
+ log.newline();
7837
+ log.dim("(QR-Code-Anzeige erfordert qrcode-terminal Paket)");
7838
+ log.newline();
7839
+ log.plain(`URL: ${url}`);
7840
+ log.newline();
7841
+ }
7842
+ function displayBackupCodes(codes) {
7843
+ log.newline();
7844
+ console.log(colors.orange.bold("Backup-Codes (speichern!)"));
7845
+ console.log(colors.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
7846
+ log.newline();
7847
+ for (let i = 0; i < codes.length; i++) {
7848
+ console.log(` ${i + 1}. ${codes[i]}`);
7849
+ }
7850
+ log.newline();
7851
+ log.warn("Diese Codes k\xF6nnen nur einmal verwendet werden!");
7852
+ log.dim("Speichere sie an einem sicheren Ort.");
7853
+ log.newline();
7854
+ }
7855
+ function displayDevices(devices) {
7856
+ log.newline();
7857
+ console.log(colors.orange.bold("Vertrauensw\xFCrdige Ger\xE4te"));
7858
+ console.log(colors.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
7859
+ log.newline();
7860
+ if (devices.length === 0) {
7861
+ log.dim("Keine Ger\xE4te registriert");
7862
+ return;
7863
+ }
7864
+ for (const device of devices) {
7865
+ const current = device.isCurrent ? colors.green(" (aktuell)") : "";
7866
+ console.log(` ${device.name}${current}`);
7867
+ log.dim(` ID: ${device.id}`);
7868
+ log.dim(` Zuletzt: ${formatRelativeTime(device.lastUsed)}`);
7869
+ if (device.expiresAt) {
7870
+ log.dim(` L\xE4uft ab: ${formatRelativeTime(device.expiresAt)}`);
7871
+ } else {
7872
+ log.dim(" L\xE4uft ab: nie");
7873
+ }
7874
+ log.newline();
7875
+ }
7876
+ }
7877
+ function formatRelativeTime(dateStr) {
7878
+ const date = new Date(dateStr);
7879
+ const now = /* @__PURE__ */ new Date();
7880
+ const diffMs = now.getTime() - date.getTime();
7881
+ const diffSec = Math.floor(diffMs / 1e3);
7882
+ const diffMin = Math.floor(diffSec / 60);
7883
+ const diffHour = Math.floor(diffMin / 60);
7884
+ const diffDay = Math.floor(diffHour / 24);
7885
+ if (diffSec < 60) return "gerade eben";
7886
+ if (diffMin < 60) return `vor ${diffMin} Minuten`;
7887
+ if (diffHour < 24) return `vor ${diffHour} Stunden`;
7888
+ if (diffDay < 30) return `vor ${diffDay} Tagen`;
7889
+ return date.toLocaleDateString("de-DE");
7890
+ }
8239
7891
 
8240
- // src/i18n/index.ts
8241
- var translations = {
8242
- de: de_default,
8243
- en: en_default,
8244
- fr: fr_default,
8245
- es: es_default
8246
- };
8247
- var currentLanguage = "de";
8248
- function setLanguage(lang) {
8249
- currentLanguage = lang;
7892
+ // src/services/auth/auth.ts
7893
+ var PORT_RANGE_START = 9876;
7894
+ var PORT_RANGE_END = 9886;
7895
+ async function loginWithOtp(email) {
7896
+ try {
7897
+ log.info(`Sende Login-Code an ${email}...`);
7898
+ const otpResponse = await api.requestOtp(email);
7899
+ if (!otpResponse.success) {
7900
+ log.error(otpResponse.message || "Fehler beim Senden des Codes");
7901
+ return false;
7902
+ }
7903
+ log.success("Code wurde gesendet!");
7904
+ log.dim(`Verbleibende Versuche: ${otpResponse.remainingAttempts}`);
7905
+ log.newline();
7906
+ const { otp } = await inquirer.prompt([
7907
+ {
7908
+ type: "input",
7909
+ name: "otp",
7910
+ message: "Code eingeben:",
7911
+ validate: (input) => {
7912
+ if (!/^\d{6}$/.test(input)) {
7913
+ return "Bitte gib den 6-stelligen Code ein";
7914
+ }
7915
+ return true;
7916
+ }
7917
+ }
7918
+ ]);
7919
+ const authResponse = await api.verifyOtp(otpResponse.token, otp);
7920
+ if (authResponse.requires2FA && authResponse.tempToken) {
7921
+ log.newline();
7922
+ log.info("2FA ist aktiviert. Bitte verifiziere dich.");
7923
+ log.newline();
7924
+ const result = await handle2FALoginFlow(authResponse.tempToken, authResponse.email || email);
7925
+ return result;
7926
+ }
7927
+ if (!authResponse.success || !authResponse.token || !authResponse.user) {
7928
+ log.error(authResponse.message || "Ung\xFCltiger Code");
7929
+ return false;
7930
+ }
7931
+ setAuth(authResponse.token, authResponse.user);
7932
+ log.newline();
7933
+ log.success(`Angemeldet als ${authResponse.user.email} (${authResponse.user.tier.toUpperCase()})`);
7934
+ return true;
7935
+ } catch (error) {
7936
+ const message = error instanceof Error ? error.message : "Unbekannter Fehler";
7937
+ log.error(`Login fehlgeschlagen: ${message}`);
7938
+ return false;
7939
+ }
8250
7940
  }
8251
- function getLanguage() {
8252
- return currentLanguage;
7941
+ async function handle2FALoginFlow(tempToken, email) {
7942
+ const MAX_ATTEMPTS = 3;
7943
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
7944
+ const { code } = await inquirer.prompt([{
7945
+ type: "input",
7946
+ name: "code",
7947
+ message: "2FA-Code (6 Ziffern oder Backup-Code):",
7948
+ validate: (input) => {
7949
+ if (/^\d{6}$/.test(input) || /^[A-Za-z0-9-]+$/.test(input)) {
7950
+ return true;
7951
+ }
7952
+ return "Bitte gib einen g\xFCltigen Code ein";
7953
+ }
7954
+ }]);
7955
+ const { rememberDevice } = await inquirer.prompt([{
7956
+ type: "confirm",
7957
+ name: "rememberDevice",
7958
+ message: "Diesem Ger\xE4t vertrauen?",
7959
+ default: true
7960
+ }]);
7961
+ try {
7962
+ const result = await api.validate2FALogin({
7963
+ tempToken,
7964
+ code,
7965
+ rememberDevice
7966
+ });
7967
+ if (result.success && result.token && result.user) {
7968
+ setAuth(result.token, result.user);
7969
+ if (result.method === "backup" && result.backupCodesRemaining !== void 0) {
7970
+ log.warn(`Backup-Code verwendet. Noch ${result.backupCodesRemaining} Codes \xFCbrig.`);
7971
+ }
7972
+ log.newline();
7973
+ log.success(`Angemeldet als ${result.user.email} (${result.user.tier.toUpperCase()})`);
7974
+ return true;
7975
+ }
7976
+ if (attempt < MAX_ATTEMPTS) {
7977
+ log.error(`Ung\xFCltiger Code. Noch ${MAX_ATTEMPTS - attempt} Versuche.`);
7978
+ }
7979
+ } catch (error) {
7980
+ const message = error instanceof Error ? error.message : "Verifizierung fehlgeschlagen";
7981
+ if (message.includes("expired") || message.includes("abgelaufen")) {
7982
+ log.error("2FA-Token abgelaufen. Bitte erneut anmelden.");
7983
+ return false;
7984
+ }
7985
+ if (attempt < MAX_ATTEMPTS) {
7986
+ log.error(`${message}. Noch ${MAX_ATTEMPTS - attempt} Versuche.`);
7987
+ }
7988
+ }
7989
+ }
7990
+ log.error("2FA-Verifizierung fehlgeschlagen");
7991
+ return false;
7992
+ }
7993
+ async function findAvailablePort() {
7994
+ for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
7995
+ const isAvailable = await checkPort(port);
7996
+ if (isAvailable) {
7997
+ return port;
7998
+ }
7999
+ }
8000
+ throw new Error("Kein freier Port f\xFCr Callback-Server gefunden");
8001
+ }
8002
+ function checkPort(port) {
8003
+ return new Promise((resolve) => {
8004
+ const server = http.createServer();
8005
+ server.once("error", () => resolve(false));
8006
+ server.once("listening", () => {
8007
+ server.close();
8008
+ resolve(true);
8009
+ });
8010
+ server.listen(port, "127.0.0.1");
8011
+ });
8012
+ }
8013
+ function getCallbackHtml(type, data) {
8014
+ const baseStyles = `
8015
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
8016
+ * { margin: 0; padding: 0; box-sizing: border-box; }
8017
+ body {
8018
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
8019
+ min-height: 100vh;
8020
+ display: flex;
8021
+ align-items: center;
8022
+ justify-content: center;
8023
+ background: #000000;
8024
+ color: #fafafa;
8025
+ position: relative;
8026
+ }
8027
+ body::before {
8028
+ content: '';
8029
+ position: fixed;
8030
+ inset: 0;
8031
+ background:
8032
+ radial-gradient(ellipse at 20% 0%, rgba(255, 107, 44, 0.12) 0%, transparent 50%),
8033
+ radial-gradient(ellipse at 80% 100%, rgba(0, 212, 255, 0.08) 0%, transparent 50%);
8034
+ pointer-events: none;
8035
+ }
8036
+ .container {
8037
+ text-align: center;
8038
+ padding: 3rem;
8039
+ max-width: 420px;
8040
+ position: relative;
8041
+ z-index: 1;
8042
+ }
8043
+ .logo {
8044
+ width: 72px;
8045
+ height: 72px;
8046
+ margin: 0 auto 2rem;
8047
+ color: #FF6B2C;
8048
+ filter: drop-shadow(0 0 30px rgba(255, 107, 44, 0.4));
8049
+ }
8050
+ .logo svg {
8051
+ width: 100%;
8052
+ height: 100%;
8053
+ }
8054
+ .brand {
8055
+ font-size: 0.875rem;
8056
+ font-weight: 600;
8057
+ letter-spacing: 0.1em;
8058
+ text-transform: uppercase;
8059
+ color: #a0a0a0;
8060
+ margin-bottom: 2rem;
8061
+ }
8062
+ h1 {
8063
+ font-size: 2rem;
8064
+ font-weight: 700;
8065
+ margin-bottom: 0.75rem;
8066
+ background: linear-gradient(135deg, #fafafa 0%, #a0a0a0 100%);
8067
+ -webkit-background-clip: text;
8068
+ -webkit-text-fill-color: transparent;
8069
+ background-clip: text;
8070
+ }
8071
+ .subtitle {
8072
+ color: #a0a0a0;
8073
+ font-size: 1rem;
8074
+ line-height: 1.6;
8075
+ }
8076
+ .email {
8077
+ color: #FF6B2C;
8078
+ font-weight: 600;
8079
+ }
8080
+ .status-icon {
8081
+ width: 72px;
8082
+ height: 72px;
8083
+ margin: 0 auto 1.5rem;
8084
+ border-radius: 50%;
8085
+ display: flex;
8086
+ align-items: center;
8087
+ justify-content: center;
8088
+ position: relative;
8089
+ }
8090
+ .status-icon::before {
8091
+ content: '';
8092
+ position: absolute;
8093
+ inset: -4px;
8094
+ border-radius: 50%;
8095
+ padding: 4px;
8096
+ background: linear-gradient(135deg, var(--icon-color) 0%, var(--icon-color-dark) 100%);
8097
+ -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
8098
+ mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
8099
+ -webkit-mask-composite: xor;
8100
+ mask-composite: exclude;
8101
+ opacity: 0.5;
8102
+ }
8103
+ .status-icon.success {
8104
+ --icon-color: #10B981;
8105
+ --icon-color-dark: #059669;
8106
+ background: linear-gradient(135deg, #10B981 0%, #059669 100%);
8107
+ box-shadow: 0 0 40px rgba(16, 185, 129, 0.4);
8108
+ }
8109
+ .status-icon.error {
8110
+ --icon-color: #EF4444;
8111
+ --icon-color-dark: #DC2626;
8112
+ background: linear-gradient(135deg, #EF4444 0%, #DC2626 100%);
8113
+ box-shadow: 0 0 40px rgba(239, 68, 68, 0.4);
8114
+ }
8115
+ .status-icon svg {
8116
+ width: 36px;
8117
+ height: 36px;
8118
+ }
8119
+ .spinner {
8120
+ width: 56px;
8121
+ height: 56px;
8122
+ margin: 0 auto 1.5rem;
8123
+ border: 3px solid rgba(255, 107, 44, 0.15);
8124
+ border-top-color: #FF6B2C;
8125
+ border-radius: 50%;
8126
+ animation: spin 1s linear infinite;
8127
+ }
8128
+ @keyframes spin {
8129
+ to { transform: rotate(360deg); }
8130
+ }
8131
+ .hint {
8132
+ margin-top: 2rem;
8133
+ padding-top: 2rem;
8134
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
8135
+ color: #666666;
8136
+ font-size: 0.875rem;
8137
+ }
8138
+ .card {
8139
+ background: rgba(13, 13, 13, 0.8);
8140
+ border: 1px solid #1a1a1a;
8141
+ border-radius: 16px;
8142
+ padding: 2.5rem;
8143
+ backdrop-filter: blur(10px);
8144
+ }
8145
+ `;
8146
+ const shivaEye = `<svg viewBox="0 -32 576 576" fill="currentColor"><defs><linearGradient id="eyeGrad" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#FF6B2C"/><stop offset="100%" style="stop-color:#FF8F5C"/></linearGradient></defs><path fill="url(#eyeGrad)" d="M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z"/></svg>`;
8147
+ const checkIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
8148
+ const xIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
8149
+ const lang = getLanguage();
8150
+ if (type === "success") {
8151
+ return `<!DOCTYPE html>
8152
+ <html lang="${lang}">
8153
+ <head>
8154
+ <meta charset="UTF-8">
8155
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8156
+ <title>${t("auth.callback.pageTitleSuccess")}</title>
8157
+ <link rel="icon" type="image/svg+xml" href="https://shiva.li/favicon.svg">
8158
+ <style>${baseStyles}</style>
8159
+ </head>
8160
+ <body>
8161
+ <div class="container">
8162
+ <div class="card">
8163
+ <div class="logo">${shivaEye}</div>
8164
+ <p class="brand">SHIVA Code</p>
8165
+ <div class="status-icon success">${checkIcon}</div>
8166
+ <h1>${t("auth.callback.successTitle")}</h1>
8167
+ <p class="subtitle">${t("auth.callback.successSubtitle")}<br><span class="email">${data.email}</span></p>
8168
+ <p class="hint">${t("auth.callback.successHint")}</p>
8169
+ </div>
8170
+ </div>
8171
+ <script>setTimeout(() => window.close(), 3000);</script>
8172
+ </body>
8173
+ </html>`;
8174
+ }
8175
+ if (type === "error") {
8176
+ return `<!DOCTYPE html>
8177
+ <html lang="${lang}">
8178
+ <head>
8179
+ <meta charset="UTF-8">
8180
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8181
+ <title>${t("auth.callback.pageTitleError")}</title>
8182
+ <link rel="icon" type="image/svg+xml" href="https://shiva.li/favicon.svg">
8183
+ <style>${baseStyles}</style>
8184
+ </head>
8185
+ <body>
8186
+ <div class="container">
8187
+ <div class="card">
8188
+ <div class="logo">${shivaEye}</div>
8189
+ <p class="brand">SHIVA Code</p>
8190
+ <div class="status-icon error">${xIcon}</div>
8191
+ <h1>${t("auth.callback.errorTitle")}</h1>
8192
+ <p class="subtitle">${data.message || t("auth.callback.errorDefault")}</p>
8193
+ <p class="hint">${t("auth.callback.errorHint")}</p>
8194
+ </div>
8195
+ </div>
8196
+ </body>
8197
+ </html>`;
8198
+ }
8199
+ return `<!DOCTYPE html>
8200
+ <html lang="${lang}">
8201
+ <head>
8202
+ <meta charset="UTF-8">
8203
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8204
+ <title>${t("auth.callback.pageTitle")}</title>
8205
+ <link rel="icon" type="image/svg+xml" href="https://shiva.li/favicon.svg">
8206
+ <style>${baseStyles}</style>
8207
+ </head>
8208
+ <body>
8209
+ <div class="container">
8210
+ <div class="card">
8211
+ <div class="logo">${shivaEye}</div>
8212
+ <p class="brand">SHIVA Code</p>
8213
+ <div class="spinner"></div>
8214
+ <h1>${t("auth.callback.waitingTitle")}</h1>
8215
+ <p class="subtitle">${t("auth.callback.waitingSubtitle")}</p>
8216
+ </div>
8217
+ </div>
8218
+ </body>
8219
+ </html>`;
8253
8220
  }
8254
- function isValidLanguage(lang) {
8255
- return ["de", "en", "fr", "es"].includes(lang);
8221
+ function startCallbackServer(port) {
8222
+ return new Promise((resolve, reject) => {
8223
+ const server = http.createServer((req, res) => {
8224
+ const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
8225
+ if (url.pathname === "/callback") {
8226
+ const token = url.searchParams.get("token");
8227
+ const userId = url.searchParams.get("userId");
8228
+ const email = url.searchParams.get("email");
8229
+ const tier = url.searchParams.get("tier");
8230
+ const error = url.searchParams.get("error");
8231
+ if (error) {
8232
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
8233
+ res.end(getCallbackHtml("error", { message: error }));
8234
+ server.close();
8235
+ reject(new Error(error));
8236
+ return;
8237
+ }
8238
+ if (!token || !userId || !email) {
8239
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
8240
+ res.end(getCallbackHtml("error", { message: "Token oder Benutzer-Daten fehlen." }));
8241
+ server.close();
8242
+ reject(new Error("Ung\xFCltige Callback-Parameter"));
8243
+ return;
8244
+ }
8245
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
8246
+ res.end(getCallbackHtml("success", { email }));
8247
+ setTimeout(() => {
8248
+ server.close();
8249
+ resolve({
8250
+ token,
8251
+ user: {
8252
+ id: parseInt(userId, 10),
8253
+ email,
8254
+ tier: tier || "free"
8255
+ }
8256
+ });
8257
+ }, 100);
8258
+ return;
8259
+ }
8260
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
8261
+ res.end(getCallbackHtml("waiting", {}));
8262
+ });
8263
+ const timeout = setTimeout(() => {
8264
+ server.close();
8265
+ reject(new Error("Login-Timeout - keine Antwort erhalten"));
8266
+ }, 3e5);
8267
+ server.once("close", () => {
8268
+ clearTimeout(timeout);
8269
+ });
8270
+ server.once("error", (err) => {
8271
+ clearTimeout(timeout);
8272
+ reject(err);
8273
+ });
8274
+ server.listen(port, "127.0.0.1");
8275
+ });
8256
8276
  }
8257
- function t(key, params) {
8258
- const keys = key.split(".");
8259
- let value = translations[currentLanguage];
8260
- for (const k of keys) {
8261
- if (value && typeof value === "object" && k in value) {
8262
- value = value[k];
8263
- } else {
8264
- value = void 0;
8265
- break;
8277
+ async function loginWithBrowser() {
8278
+ const config = getConfig();
8279
+ try {
8280
+ const port = await findAvailablePort();
8281
+ const callbackUrl = `http://127.0.0.1:${port}/callback`;
8282
+ const baseUrl = config.apiEndpoint.replace("/api", "");
8283
+ const lang = getLanguage();
8284
+ const loginUrl = `${baseUrl}/auth/cli-login?callback=${encodeURIComponent(callbackUrl)}&lang=${lang}`;
8285
+ const serverPromise = startCallbackServer(port);
8286
+ log.newline();
8287
+ log.info("\xD6ffne Browser zur Anmeldung...");
8288
+ try {
8289
+ await open(loginUrl);
8290
+ } catch {
8266
8291
  }
8267
- }
8268
- if (typeof value !== "string") {
8269
- value = keys.reduce(
8270
- (obj, k) => obj && typeof obj === "object" && k in obj ? obj[k] : void 0,
8271
- translations.de
8272
- );
8273
- }
8274
- if (typeof value !== "string") {
8275
- return key;
8276
- }
8277
- if (params) {
8278
- for (const [param, val] of Object.entries(params)) {
8279
- value = value.replace(new RegExp(`\\{${param}\\}`, "g"), String(val));
8292
+ log.newline();
8293
+ log.plain("Bitte \xF6ffne diese URL in deinem Browser um die Anmeldung abzuschlie\xDFen:");
8294
+ log.newline();
8295
+ console.log(` ${colors.cyan(loginUrl)}`);
8296
+ log.newline();
8297
+ log.dim("Warte auf Anmeldung... (Timeout: 5 Minuten)");
8298
+ log.newline();
8299
+ const result = await serverPromise;
8300
+ setAuth(result.token, result.user);
8301
+ try {
8302
+ const tfaStatus = await twoFactorService.getStatus();
8303
+ if (tfaStatus.enabled) {
8304
+ log.newline();
8305
+ log.info("2FA ist aktiviert. Bitte verifiziere dich.");
8306
+ log.newline();
8307
+ const isDeviceTrusted = await deviceTrustService.isDeviceTrusted();
8308
+ if (!isDeviceTrusted) {
8309
+ const verified = await prompt2FAVerification();
8310
+ if (!verified) {
8311
+ clearAuth();
8312
+ log.error("2FA-Verifizierung fehlgeschlagen");
8313
+ return false;
8314
+ }
8315
+ const { trustDevice } = await inquirer.prompt([{
8316
+ type: "confirm",
8317
+ name: "trustDevice",
8318
+ message: "Diesem Ger\xE4t vertrauen?",
8319
+ default: true
8320
+ }]);
8321
+ if (trustDevice) {
8322
+ try {
8323
+ await deviceTrustService.trustDevice();
8324
+ log.success("Ger\xE4t als vertrauensw\xFCrdig markiert");
8325
+ } catch {
8326
+ }
8327
+ }
8328
+ } else {
8329
+ log.dim("Ger\xE4t ist vertrauensw\xFCrdig - 2FA \xFCbersprungen");
8330
+ }
8331
+ }
8332
+ } catch {
8280
8333
  }
8281
- }
8282
- return value;
8283
- }
8284
- function tArray(key) {
8285
- const keys = key.split(".");
8286
- let value = translations[currentLanguage];
8287
- for (const k of keys) {
8288
- if (value && typeof value === "object" && k in value) {
8289
- value = value[k];
8290
- } else {
8291
- value = void 0;
8292
- break;
8334
+ log.success(`Angemeldet als ${result.user.email} (${result.user.tier.toUpperCase()})`);
8335
+ return true;
8336
+ } catch (error) {
8337
+ const message = error instanceof Error ? error.message : "Unbekannter Fehler";
8338
+ log.error(`Login fehlgeschlagen: ${message}`);
8339
+ log.newline();
8340
+ log.info("Fallback: Manueller Token-Eintrag");
8341
+ const fallbackLang = getLanguage();
8342
+ const loginUrl = `${config.apiEndpoint.replace("/api", "")}/auth/cli-login?lang=${fallbackLang}`;
8343
+ log.dim(`URL: ${loginUrl}`);
8344
+ log.newline();
8345
+ const { useManual } = await inquirer.prompt([{
8346
+ type: "confirm",
8347
+ name: "useManual",
8348
+ message: "Token manuell eingeben?",
8349
+ default: true
8350
+ }]);
8351
+ if (!useManual) {
8352
+ return false;
8353
+ }
8354
+ try {
8355
+ await open(loginUrl);
8356
+ } catch {
8357
+ log.dim("Browser konnte nicht ge\xF6ffnet werden");
8358
+ log.dim(`\xD6ffne manuell: ${loginUrl}`);
8359
+ }
8360
+ const { token } = await inquirer.prompt([{
8361
+ type: "password",
8362
+ name: "token",
8363
+ message: "Token:",
8364
+ mask: "*"
8365
+ }]);
8366
+ if (!token) {
8367
+ log.error("Kein Token eingegeben");
8368
+ return false;
8369
+ }
8370
+ setAuth(token, { id: 0, email: "", tier: "free" });
8371
+ try {
8372
+ const response = await api.getCurrentUser();
8373
+ setAuth(token, response.user);
8374
+ log.success(`Angemeldet als ${response.user.email}`);
8375
+ return true;
8376
+ } catch {
8377
+ clearAuth();
8378
+ log.error("Ung\xFCltiger Token");
8379
+ return false;
8293
8380
  }
8294
8381
  }
8295
- if (!Array.isArray(value)) {
8296
- value = keys.reduce(
8297
- (obj, k) => obj && typeof obj === "object" && k in obj ? obj[k] : void 0,
8298
- translations.de
8299
- );
8300
- }
8301
- return Array.isArray(value) ? value : [];
8302
8382
  }
8303
- function initializeLanguage(storedLang) {
8304
- if (storedLang && isValidLanguage(storedLang)) {
8305
- setLanguage(storedLang);
8306
- return;
8307
- }
8308
- try {
8309
- const systemLocale = Intl.DateTimeFormat().resolvedOptions().locale;
8310
- const langCode = systemLocale.split("-")[0].toLowerCase();
8311
- if (isValidLanguage(langCode)) {
8312
- setLanguage(langCode);
8313
- return;
8383
+ function logout() {
8384
+ clearAuth();
8385
+ log.success("Abgemeldet");
8386
+ }
8387
+ async function prompt2FAVerification() {
8388
+ const MAX_ATTEMPTS = 3;
8389
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
8390
+ const { method } = await inquirer.prompt([{
8391
+ type: "list",
8392
+ name: "method",
8393
+ message: "Verifizierungsmethode:",
8394
+ choices: [
8395
+ { name: "Authenticator App Code", value: "totp" },
8396
+ { name: "Backup Code", value: "backup" },
8397
+ { name: "Abbrechen", value: "cancel" }
8398
+ ]
8399
+ }]);
8400
+ if (method === "cancel") {
8401
+ return false;
8402
+ }
8403
+ const promptMessage = method === "totp" ? "6-stelliger Code aus deiner Authenticator App:" : "Backup-Code (Format: XXX-XXX-XXX-XXX):";
8404
+ const { code } = await inquirer.prompt([{
8405
+ type: "input",
8406
+ name: "code",
8407
+ message: promptMessage,
8408
+ validate: (input) => {
8409
+ if (method === "totp") {
8410
+ return /^\d{6}$/.test(input) || "Bitte gib einen 6-stelligen Code ein";
8411
+ }
8412
+ return input.length > 0 || "Bitte gib einen Backup-Code ein";
8413
+ }
8414
+ }]);
8415
+ try {
8416
+ let verified = false;
8417
+ if (method === "totp") {
8418
+ verified = await twoFactorService.verifyCode(code);
8419
+ } else {
8420
+ verified = await twoFactorService.verifyBackupCode(code);
8421
+ }
8422
+ if (verified) {
8423
+ log.success("2FA-Verifizierung erfolgreich");
8424
+ return true;
8425
+ }
8426
+ } catch (error) {
8427
+ }
8428
+ if (attempt < MAX_ATTEMPTS) {
8429
+ log.error(`Ung\xFCltiger Code. Noch ${MAX_ATTEMPTS - attempt} Versuche.`);
8314
8430
  }
8315
- } catch {
8316
8431
  }
8317
- setLanguage("de");
8432
+ return false;
8318
8433
  }
8319
8434
 
8320
8435
  // src/commands/auth/login.ts