omnibiofex 2.8.4 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/auth.js CHANGED
@@ -1,354 +1,215 @@
1
- const http = require('http');
2
- const { URL } = require('url');
3
- const axios = require('axios');
4
1
  const chalk = require('chalk');
5
- const inquirer = require('inquirer');
6
- const Conf = require('conf');
7
- const { sendSignInLinkToEmail } = require('firebase/auth');
8
- const { app, auth, db } = require('./firebase');
9
-
10
- // Use the shared auth instance
11
- const config = new Conf({
12
- projectName: 'omnibiofex',
13
- schema: {
14
- token: { type: 'string' },
15
- refreshToken: { type: 'string' },
16
- expiresAt: { type: 'number' }
17
- }
18
- });
19
-
20
- const LOCAL_PORT = 8765;
21
- const CALLBACK_PATH = '/auth/callback';
22
-
23
- async function login() {
24
- console.log(chalk.hex('#F24E1E')('\nšŸ” OmniBioFex X Authentication\n'));
25
-
26
- if (isAuthenticated()) {
27
- const { confirm } = await inquirer.prompt([{
28
- type: 'confirm',
29
- name: 'confirm',
30
- message: 'You are already logged in. Login again?',
31
- default: false
32
- }]);
33
- if (!confirm) {
34
- console.log(chalk.gray('Login cancelled.'));
35
- return;
36
- }
37
- // Clear old tokens
38
- config.delete('authToken');
39
- config.delete('refreshToken');
40
- config.delete('tokenExpiry');
41
- }
42
-
43
- const { email } = await inquirer.prompt([{
44
- type: 'input',
45
- name: 'email',
46
- message: 'Enter your email:',
47
- validate: (input) => {
48
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
49
- return emailRegex.test(input) || 'Please enter a valid email';
50
- }
51
- }]);
52
-
53
- console.log(chalk.gray('\nšŸ“§ Sending magic link...'));
54
-
55
- let server = null;
56
- let spinner = null;
57
- let timeout = null;
58
-
59
- try {
60
- server = startLocalServer();
61
-
62
- const actionCodeSettings = {
63
- url: `https://x.omnibiofex.cloud/auth?cli=true`,
64
- handleCodeInApp: true
65
- };
66
-
67
- await sendSignInLinkToEmail(auth, email, actionCodeSettings);
68
-
69
- config.set('pendingEmail', email);
70
- config.set('pendingLogin', true);
71
-
72
- console.log(chalk.green(`\nāœ“ Magic link sent to ${email}`));
73
- console.log(chalk.gray('\nšŸ“¬ Check your inbox and click the link to complete login.'));
74
- console.log(chalk.gray('ā³ Waiting for authentication... (press Ctrl+C to cancel)\n'));
75
-
76
- const frames = ['ā ‹', 'ā ™', 'ā ¹', 'ā ø', 'ā ¼', 'ā “', 'ā ¦', 'ā §', 'ā ‡', 'ā '];
77
- let i = 0;
78
- spinner = setInterval(() => {
79
- process.stdout.write(`\r${chalk.hex('#F24E1E')(frames[i])} ${chalk.gray('Waiting for magic link click...')}`);
80
- i = (i + 1) % frames.length;
81
- }, 80);
82
-
83
- timeout = setTimeout(() => {
84
- cleanup();
85
- console.log(chalk.red('\n\nāœ— Authentication timed out. Please try again.'));
86
- config.delete('pendingLogin');
87
- config.delete('pendingEmail');
88
- process.exit(1);
89
- }, 5 * 60 * 1000);
90
-
91
- const authData = await waitForAuth();
92
-
93
- cleanup();
94
-
95
- if (authData && authData.token) {
96
- // šŸ”„ Validate token format before storing
97
- if (!authData.token.startsWith('eyJ')) {
98
- throw new Error('Invalid token format received');
99
- }
2
+ const http = require('http');
3
+ const url = require('url');
4
+ const open = require('open');
5
+ const user = require('./user');
100
6
 
101
- config.set('authToken', authData.token);
102
- if (authData.refreshToken) {
103
- config.set('refreshToken', authData.refreshToken);
104
- }
105
- config.set('userEmail', email);
106
- config.set('tokenExpiry', Date.now() + (55 * 60 * 1000)); // 55 minutes
107
- config.delete('pendingLogin');
108
- config.delete('pendingEmail');
109
-
110
- console.log(chalk.green('\n\nāœ“ Successfully authenticated!'));
111
- console.log(chalk.hex('#F24E1E')(`\nšŸŽ‰ Welcome to OmniBioFex X, ${email}!`));
112
- console.log(chalk.gray('You can now use all CLI commands.\n'));
113
-
114
- process.exit(0);
115
- } else {
116
- throw new Error('No authentication data received');
117
- }
7
+ const FIREBASE_CONFIG = {
8
+ apiKey: "AIzaSyDlgXId4pLlYqm-MDuhfz3dLH24KBRHkw8",
9
+ authDomain: "x.omnibiofex.cloud",
10
+ projectId: "omnibiofex-x"
11
+ };
118
12
 
119
- } catch (error) {
120
- cleanup();
121
- console.error(chalk.red(`\nāœ— Authentication failed: ${error.message}`));
122
- config.delete('pendingLogin');
123
- config.delete('pendingEmail');
124
- process.exit(1);
125
- }
13
+ const GOOGLE_CLIENT_ID = "292246591666-bc9jk6astsk1rha03cmuulda66db7c1q.apps.googleusercontent.com";
14
+ const GOOGLE_CLIENT_SECRET = "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";
15
+ const REDIRECT_URI = "http://localhost:8765/callback";
16
+ const SCOPES = "openid email profile";
126
17
 
127
- function cleanup() {
128
- if (spinner) {
129
- clearInterval(spinner);
130
- spinner = null;
131
- process.stdout.write('\n');
132
- }
133
- if (timeout) {
134
- clearTimeout(timeout);
135
- timeout = null;
136
- }
137
- if (server) {
138
- server.close();
139
- server = null;
140
- }
141
- }
18
+ function isAuthenticated() {
19
+ const token = user.getToken();
20
+ return !!token && !user.isTokenExpired();
142
21
  }
143
22
 
144
- function startLocalServer() {
145
- const server = http.createServer((req, res) => {
146
- const url = new URL(req.url, `http://localhost:${LOCAL_PORT}`);
147
-
148
- if (url.pathname === '/favicon.ico') {
149
- res.writeHead(204);
150
- res.end();
151
- return;
152
- }
153
-
154
- console.log('Local server received request:', req.url);
155
-
156
- if (url.pathname === CALLBACK_PATH) {
157
- let token = url.searchParams.get('token');
158
- let refreshToken = url.searchParams.get('refreshToken');
159
- const error = url.searchParams.get('error');
160
-
161
- if (token && token.includes(' ')) {
162
- console.log(chalk.yellow('āš ļø Token contains spaces, fixing...'));
163
- token = token.replace(/ /g, '+');
164
- }
165
-
166
- if (refreshToken && refreshToken.includes(' ')) {
167
- refreshToken = refreshToken.replace(/ /g, '+');
168
- }
169
-
170
- if (token) {
171
- console.log('āœ“ Received auth token from browser');
172
- console.log('Token length:', token.length);
173
- console.log('Token starts with:', token.substring(0, 20));
174
-
175
- if (!token.startsWith('eyJ')) {
176
- console.error('āœ— Invalid token format');
177
- res.writeHead(400, { 'Content-Type': 'text/html' });
178
- res.end('<h1>Invalid Token</h1><p>The authentication token is malformed.</p>');
179
- global._authData = null;
180
- return;
23
+ async function getAuthToken() {
24
+ let token = user.getToken();
25
+ const refreshToken = user.getRefreshToken();
26
+
27
+ if (!token) throw new Error('Not authenticated. Please run: obx login');
28
+
29
+ if (user.isTokenExpired() && refreshToken) {
30
+ try {
31
+ console.log(chalk.gray('šŸ”„ Refreshing authentication token...'));
32
+ const response = await fetch(
33
+ `https://securetoken.googleapis.com/v1/token?key=${FIREBASE_CONFIG.apiKey}`,
34
+ {
35
+ method: 'POST',
36
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
37
+ body: `grant_type=refresh_token&refresh_token=${refreshToken}`
181
38
  }
39
+ );
40
+
41
+ if (response.ok) {
42
+ const data = await response.json();
43
+ const parts = data.id_token.split('.');
44
+ const payload = JSON.parse(
45
+ Buffer.from(parts[1].replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8')
46
+ );
182
47
 
183
- res.writeHead(200, { 'Content-Type': 'text/html' });
184
- res.end(`
185
- <!DOCTYPE html>
186
- <html>
187
- <head>
188
- <title>OmniBioFex X - Authentication Successful</title>
189
- <style>
190
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
191
- background: #F4F4F0; color: #0F0F0F; display: flex; align-items: center;
192
- justify-content: center; min-height: 100vh; margin: 0;
193
- background-image: radial-gradient(#E0E0DB 1px, transparent 1px);
194
- background-size: 40px 40px; }
195
- .card { background: white; border: 1px solid #E0E0DB; padding: 48px;
196
- max-width: 500px; text-align: center; }
197
- .check { width: 64px; height: 64px; border-radius: 50%; background: #10b981;
198
- color: white; font-size: 32px; display: flex; align-items: center;
199
- justify-content: center; margin: 0 auto 24px; }
200
- h1 { margin: 0 0 12px; font-size: 24px; }
201
- p { color: #737373; margin: 0 0 24px; }
202
- </style>
203
- </head>
204
- <body>
205
- <div class="card">
206
- <div class="check">āœ“</div>
207
- <h1>Authentication Successful!</h1>
208
- <p>You can close this window and return to your terminal.</p>
209
- </div>
210
- </body>
211
- </html>
212
- `);
213
-
214
- global._authData = { token, refreshToken };
48
+ user.setToken(data.id_token, data.refresh_token,
49
+ data.expires_in ? Math.floor(Date.now()/1000) + parseInt(data.expires_in) : null,
50
+ { email: payload.email, uid: payload.user_id || payload.sub, name: payload.name }
51
+ );
52
+ token = data.id_token;
53
+ console.log(chalk.green('āœ“ Token refreshed successfully!'));
215
54
  } else {
216
- console.error('āœ— No token received, error:', error);
217
- res.writeHead(200, { 'Content-Type': 'text/html' });
218
- res.end(`
219
- <!DOCTYPE html>
220
- <html>
221
- <head>
222
- <title>Authentication Failed</title>
223
- <style>
224
- body { font-family: sans-serif; background: #F4F4F0; display: flex;
225
- align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
226
- .card { background: white; padding: 48px; text-align: center; border: 1px solid #E0E0DB; }
227
- h1 { color: #F24E1E; }
228
- </style>
229
- </head>
230
- <body>
231
- <div class="card">
232
- <h1>Authentication Failed</h1>
233
- <p>${error || 'Unknown error'}</p>
234
- </div>
235
- </body>
236
- </html>
237
- `);
238
- global._authData = null;
55
+ throw new Error('Token refresh failed');
239
56
  }
240
- } else {
241
- res.writeHead(404);
242
- res.end('Not found');
57
+ } catch (error) {
58
+ throw new Error('Token refresh failed. Please run: obx login');
243
59
  }
244
- });
60
+ }
61
+
62
+ return token;
63
+ }
245
64
 
246
- server.listen(LOCAL_PORT, () => {
247
- console.log(`Local auth server listening on port ${LOCAL_PORT}`);
65
+ function buildGoogleAuthUrl() {
66
+ const params = new URLSearchParams({
67
+ client_id: GOOGLE_CLIENT_ID,
68
+ redirect_uri: REDIRECT_URI,
69
+ response_type: 'code',
70
+ scope: SCOPES,
71
+ access_type: 'offline',
72
+ prompt: 'consent'
248
73
  });
249
-
250
- return server;
74
+ return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
251
75
  }
252
76
 
253
- function waitForAuth() {
254
- return new Promise((resolve) => {
255
- const checkInterval = setInterval(() => {
256
- if (global._authData !== undefined) {
257
- clearInterval(checkInterval);
258
- const authData = global._authData;
259
- delete global._authData;
260
- resolve(authData);
261
- }
262
- }, 500);
77
+ async function exchangeGoogleCode(code) {
78
+ const response = await fetch('https://oauth2.googleapis.com/token', {
79
+ method: 'POST',
80
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
81
+ body: new URLSearchParams({
82
+ code, client_id: GOOGLE_CLIENT_ID, client_secret: GOOGLE_CLIENT_SECRET,
83
+ redirect_uri: REDIRECT_URI, grant_type: 'authorization_code'
84
+ })
263
85
  });
86
+ if (!response.ok) {
87
+ const error = await response.json();
88
+ throw new Error(error.error_description || 'Failed to exchange code');
89
+ }
90
+ return await response.json();
264
91
  }
265
92
 
266
- async function refreshAuthToken() {
267
- const refreshToken = config.get('refreshToken');
268
-
269
- if (!refreshToken) {
270
- throw new Error('No refresh token available. Please login again.');
93
+ async function signInWithGoogle(googleIdToken, googleAccessToken) {
94
+ const response = await fetch(
95
+ `https://identitytoolkit.googleapis.com/v1/accounts:signInWithIdp?key=${FIREBASE_CONFIG.apiKey}`,
96
+ {
97
+ method: 'POST',
98
+ headers: { 'Content-Type': 'application/json' },
99
+ body: JSON.stringify({
100
+ postBody: `id_token=${googleIdToken}&providerId=google.com&access_token=${googleAccessToken}`,
101
+ requestUri: REDIRECT_URI,
102
+ returnIdpCredential: true,
103
+ returnSecureToken: true
104
+ })
105
+ }
106
+ );
107
+ if (!response.ok) {
108
+ const error = await response.json();
109
+ throw new Error(error.error?.message || 'Failed to sign in with Google');
271
110
  }
111
+ return await response.json();
112
+ }
113
+
114
+ async function login() {
115
+ console.log(chalk.hex('#F24E1E')('\n═══════════════════════════════════════════════════════════'));
116
+ console.log(chalk.white.bold('šŸ” OmniBioFex X Authentication'));
117
+ console.log(chalk.hex('#F24E1E')('═══════════════════════════════════════════════════════════\n'));
272
118
 
273
- try {
274
- console.log(chalk.gray('šŸ”„ Refreshing authentication token...'));
119
+ if (isAuthenticated()) {
120
+ const email = user.getCurrentUserEmail();
121
+ const name = user.getCurrentUserName();
122
+ console.log(chalk.gray(` Currently logged in as: ${chalk.white(name || email || 'Unknown')}\n`));
123
+
124
+ const inquirer = require('inquirer');
125
+ const { relogin } = await inquirer.prompt([{
126
+ type: 'confirm', name: 'relogin',
127
+ message: 'Login with a different Google account?', default: false
128
+ }]);
275
129
 
276
- const apiKey = app.options.apiKey; // Get API key from the shared Firebase app
130
+ if (!relogin) { console.log(chalk.yellow('Login cancelled.')); return; }
131
+ user.clearTokens();
132
+ }
277
133
 
278
- const response = await axios.post(
279
- `https://securetoken.googleapis.com/v1/token?key=${apiKey}`,
280
- new URLSearchParams({
281
- grant_type: 'refresh_token',
282
- refresh_token: refreshToken
283
- }).toString(),
284
- {
285
- headers: {
286
- 'Content-Type': 'application/x-www-form-urlencoded'
134
+ console.log(chalk.gray('🌐 Opening Google sign-in in your browser...\n'));
135
+ const authUrl = buildGoogleAuthUrl();
136
+ let timeoutId = null;
137
+
138
+ const server = http.createServer(async (req, res) => {
139
+ const parsedUrl = url.parse(req.url, true);
140
+
141
+ if (parsedUrl.pathname === '/callback') {
142
+ const { code, error } = parsedUrl.query;
143
+
144
+ if (error) {
145
+ res.writeHead(400, { 'Content-Type': 'text/html' });
146
+ res.end(`<html><body style="font-family:sans-serif;text-align:center;padding:50px;"><h1 style="color:#EF4444;">āœ— Auth Failed</h1><p>${error}</p></body></html>`);
147
+ if (timeoutId) clearTimeout(timeoutId);
148
+ server.close(() => { console.error(chalk.red(`āœ— Error: ${error}`)); process.exit(1); });
149
+ return;
150
+ }
151
+
152
+ if (code) {
153
+ try {
154
+ console.log(chalk.gray('šŸ”„ Exchanging authorization code...'));
155
+ const googleTokens = await exchangeGoogleCode(code);
156
+ console.log(chalk.gray('šŸ”„ Signing in with Firebase...'));
157
+ const firebaseResponse = await signInWithGoogle(googleTokens.id_token, googleTokens.access_token);
158
+
159
+ const expiresIn = parseInt(firebaseResponse.expiresIn);
160
+ user.setToken(firebaseResponse.idToken, firebaseResponse.refreshToken,
161
+ Math.floor(Date.now()/1000) + expiresIn,
162
+ { email: firebaseResponse.email, uid: firebaseResponse.localId, name: firebaseResponse.displayName }
163
+ );
164
+
165
+ res.writeHead(200, { 'Content-Type': 'text/html' });
166
+ res.end(`<html><body style="font-family:sans-serif;text-align:center;padding:50px;background:#F3EFE0;"><h1 style="color:#10B981;">āœ“ Success!</h1><p>Welcome, ${firebaseResponse.displayName || firebaseResponse.email}!</p><p>You can close this window.</p></body></html>`);
167
+
168
+ console.log(chalk.green('\nāœ“ Successfully authenticated with Google!'));
169
+ console.log(chalk.hex('#F24E1E')(`\nšŸŽ‰ Welcome to OmniBioFex X, ${firebaseResponse.displayName || firebaseResponse.email}!`));
170
+ console.log(chalk.gray('You can now use all CLI commands.\n'));
171
+
172
+ if (timeoutId) clearTimeout(timeoutId);
173
+ server.close(() => process.exit(0));
174
+ } catch (error) {
175
+ res.writeHead(500, { 'Content-Type': 'text/html' });
176
+ res.end(`<html><body style="font-family:sans-serif;text-align:center;padding:50px;"><h1 style="color:#EF4444;">āœ— Failed</h1><p>${error.message}</p></body></html>`);
177
+ if (timeoutId) clearTimeout(timeoutId);
178
+ server.close(() => { console.error(chalk.red(`\nāœ— Failed: ${error.message}`)); process.exit(1); });
287
179
  }
288
180
  }
289
- );
290
-
291
- const { id_token, refresh_token, expires_in } = response.data;
292
-
293
- if (!id_token || !id_token.startsWith('eyJ')) {
294
- throw new Error('Invalid token received from refresh endpoint');
295
181
  }
182
+ });
296
183
 
297
- config.set('authToken', id_token);
298
- config.set('refreshToken', refresh_token);
299
- config.set('tokenExpiry', Date.now() + ((expires_in - 300) * 1000));
184
+ server.listen(8765, () => {
185
+ console.log(chalk.gray(' Local auth server listening on http://localhost:8765'));
186
+ open(authUrl).catch(() => {
187
+ console.log(chalk.gray(' If browser didn\'t open, visit:'));
188
+ console.log(chalk.blue(` ${authUrl}\n`));
189
+ });
190
+ });
300
191
 
301
- console.log(chalk.green('āœ“ Token refreshed successfully!'));
302
- return id_token;
303
- } catch (error) {
304
- console.error(chalk.red('āœ— Failed to refresh token:', error.message));
305
- throw new Error('Token refresh failed. Please login again.');
306
- }
192
+ timeoutId = setTimeout(() => {
193
+ server.close(() => { console.error(chalk.red('\nāœ— Authentication timeout.\n')); process.exit(1); });
194
+ }, 300000);
307
195
  }
308
196
 
309
- function logout() {
310
- config.delete('authToken');
311
- config.delete('refreshToken');
312
- config.delete('userId');
313
- config.delete('userEmail');
314
- config.delete('tokenExpiry');
315
- console.log(chalk.green('āœ“ Logged out successfully'));
316
- process.exit(0);
317
- }
197
+ async function logout() {
198
+ console.log(chalk.hex('#F24E1E')('\n═══════════════════════════════════════════════════════════'));
199
+ console.log(chalk.white.bold('🚪 Sign Out'));
200
+ console.log(chalk.hex('#F24E1E')('═══════════════════════════════════════════════════════════\n'));
318
201
 
319
- function isAuthenticated() {
320
- return config.get('authToken') !== null;
321
- }
202
+ if (!user.getToken()) { console.log(chalk.yellow(' You are not logged in.')); return; }
322
203
 
323
- function isTokenExpired() {
324
- const expiry = config.get('tokenExpiry');
325
- if (!expiry) return true;
326
- return Date.now() >= expiry;
327
- }
204
+ const inquirer = require('inquirer');
205
+ const { confirm } = await inquirer.prompt([{
206
+ type: 'confirm', name: 'confirm',
207
+ message: 'Are you sure you want to sign out?', default: false
208
+ }]);
328
209
 
329
- async function getAuthToken() {
330
- if (isTokenExpired()) {
331
- try {
332
- await refreshAuthToken();
333
- } catch (error) {
334
- console.error(chalk.red(error.message));
335
- process.exit(1);
336
- }
337
- }
338
-
339
- const token = config.get('authToken');
340
- if (!token) {
341
- console.error(chalk.red('Not authenticated. Please run: obx login'));
342
- process.exit(1);
343
- }
344
-
345
- if (!token.startsWith('eyJ')) {
346
- console.error(chalk.red('Stored token is corrupted. Please login again.'));
347
- config.delete('authToken');
348
- process.exit(1);
349
- }
350
-
351
- return token;
210
+ if (!confirm) { console.log(chalk.yellow(' Logout cancelled.')); return; }
211
+ user.clearTokens();
212
+ console.log(chalk.green(' āœ“ Logged out successfully\n'));
352
213
  }
353
214
 
354
- module.exports = { login, logout, isAuthenticated, getAuthToken, refreshAuthToken };
215
+ module.exports = { login, logout, getAuthToken, isAuthenticated };