omnibiofex 2.8.5 ā 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/bin/obx +30 -158
- package/package.json +13 -28
- package/src/auth.js +181 -381
- package/src/commands/account.js +35 -88
- package/src/commands/data.js +40 -114
- package/src/commands/debug.js +28 -25
- package/src/commands/earnings.js +31 -86
- package/src/commands/mission.js +194 -98
- package/src/commands/morning.js +39 -107
- package/src/commands/open.js +26 -36
- package/src/commands/publish.js +81 -126
- package/src/commands/research.js +148 -281
- package/src/commands/timeline.js +15 -54
- package/src/firebase.js +4 -14
- package/src/user.js +94 -0
- package/src/utils/display.js +27 -856
- package/src/api.js +0 -72
- package/src/config.js +0 -16
package/src/auth.js
CHANGED
|
@@ -1,415 +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
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
241
|
-
|
|
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
|
-
|
|
247
|
-
|
|
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
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
+
}
|
|
272
113
|
|
|
273
|
-
|
|
274
|
-
|
|
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'));
|
|
118
|
+
|
|
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
|
-
|
|
130
|
+
if (!relogin) { console.log(chalk.yellow('Login cancelled.')); return; }
|
|
131
|
+
user.clearTokens();
|
|
132
|
+
}
|
|
277
133
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
console.error(chalk.red('ā Failed to refresh token:', error.message));
|
|
305
|
-
throw new Error('Token refresh failed. Please login again.');
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
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
|
-
}
|
|
318
|
-
|
|
319
|
-
function isAuthenticated() {
|
|
320
|
-
return config.get('authToken') !== null;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function isTokenExpired() {
|
|
324
|
-
const expiry = config.get('tokenExpiry');
|
|
325
|
-
if (!expiry) return true;
|
|
326
|
-
return Date.now() >= expiry;
|
|
327
|
-
}
|
|
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
|
+
});
|
|
328
191
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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;
|
|
192
|
+
timeoutId = setTimeout(() => {
|
|
193
|
+
server.close(() => { console.error(chalk.red('\nā Authentication timeout.\n')); process.exit(1); });
|
|
194
|
+
}, 300000);
|
|
352
195
|
}
|
|
353
196
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
*/
|
|
359
|
-
function base64UrlDecode(str) {
|
|
360
|
-
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
|
|
361
|
-
while (base64.length % 4) {
|
|
362
|
-
base64 += '=';
|
|
363
|
-
}
|
|
364
|
-
return Buffer.from(base64, 'base64').toString('utf8');
|
|
365
|
-
}
|
|
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'));
|
|
366
201
|
|
|
367
|
-
|
|
368
|
-
* Decode JWT token to extract user info
|
|
369
|
-
*/
|
|
370
|
-
function decodeToken() {
|
|
371
|
-
const token = config.get('authToken'); // fixed key name
|
|
372
|
-
if (!token) return null;
|
|
373
|
-
|
|
374
|
-
try {
|
|
375
|
-
const parts = token.split('.');
|
|
376
|
-
if (parts.length !== 3) return null;
|
|
377
|
-
|
|
378
|
-
const payload = parts[1];
|
|
379
|
-
const decoded = base64UrlDecode(payload);
|
|
380
|
-
const data = JSON.parse(decoded);
|
|
381
|
-
|
|
382
|
-
return {
|
|
383
|
-
uid: data.user_id || data.sub,
|
|
384
|
-
email: data.email,
|
|
385
|
-
exp: data.exp
|
|
386
|
-
};
|
|
387
|
-
} catch (error) {
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
202
|
+
if (!user.getToken()) { console.log(chalk.yellow(' You are not logged in.')); return; }
|
|
391
203
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
return decoded ? decoded.uid : null;
|
|
398
|
-
}
|
|
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
|
+
}]);
|
|
399
209
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
function getCurrentUserEmail() {
|
|
404
|
-
const decoded = decodeToken();
|
|
405
|
-
return decoded ? decoded.email : null;
|
|
210
|
+
if (!confirm) { console.log(chalk.yellow(' Logout cancelled.')); return; }
|
|
211
|
+
user.clearTokens();
|
|
212
|
+
console.log(chalk.green(' ā Logged out successfully\n'));
|
|
406
213
|
}
|
|
407
214
|
|
|
408
|
-
module.exports = {
|
|
409
|
-
login,
|
|
410
|
-
logout,
|
|
411
|
-
getAuthToken,
|
|
412
|
-
isAuthenticated,
|
|
413
|
-
getCurrentUserUid,
|
|
414
|
-
getCurrentUserEmail
|
|
415
|
-
};
|
|
215
|
+
module.exports = { login, logout, getAuthToken, isAuthenticated };
|