nothumanallowed 14.1.32 → 14.1.34

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "14.1.32",
3
+ "version": "14.1.34",
4
4
  "description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/constants.mjs CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
 
8
- export const VERSION = '14.1.32';
8
+ export const VERSION = '14.1.34';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -209,6 +209,74 @@ async function buildRouter() {
209
209
  return router;
210
210
  }
211
211
 
212
+ // ── Google OAuth Enterprise Middleware ──────────────────────────────────────
213
+
214
+ function isGoogleAPIRoute(pathname) {
215
+ const googleRoutes = [
216
+ '/api/email/read',
217
+ '/api/email/send',
218
+ '/api/email/mark-read',
219
+ '/api/email/mark-all-read',
220
+ '/api/calendar',
221
+ '/api/calendar/upcoming',
222
+ '/api/drive',
223
+ '/api/google-auth'
224
+ ];
225
+ return googleRoutes.some(route => pathname.startsWith(route));
226
+ }
227
+
228
+ async function validateGoogleOAuth(req) {
229
+ try {
230
+ const { loadConfig } = await import('../config.mjs');
231
+ const config = loadConfig();
232
+
233
+ if (!config.google?.clientId && !config.google?.tokens?.access_token) {
234
+ return {
235
+ status: 401,
236
+ body: {
237
+ error: 'Google OAuth not configured',
238
+ authRequired: true,
239
+ message: 'Gmail and Calendar require authentication. Setup OAuth to continue.',
240
+ setupInstructions: [
241
+ 'Go to Google Cloud Console: https://console.cloud.google.com/apis/credentials',
242
+ 'Create OAuth 2.0 Client ID (Desktop Application)',
243
+ 'Enable Gmail API and Calendar API',
244
+ 'Run: nha config set google-client-id YOUR_CLIENT_ID',
245
+ 'Run: nha config set google-client-secret YOUR_CLIENT_SECRET',
246
+ 'Run: nha google auth'
247
+ ]
248
+ }
249
+ };
250
+ }
251
+
252
+ if (config.google?.tokens?.access_token) {
253
+ // Check if token is expired (basic heuristic)
254
+ const expiryTime = config.google.tokens.expiry_date;
255
+ if (expiryTime && Date.now() > expiryTime) {
256
+ return {
257
+ status: 401,
258
+ body: {
259
+ error: 'Google OAuth token expired',
260
+ authRequired: true,
261
+ message: 'Your Google authentication has expired. Please re-authenticate.',
262
+ action: 'Run: nha google auth'
263
+ }
264
+ };
265
+ }
266
+ }
267
+
268
+ return null; // OAuth is valid
269
+ } catch (e) {
270
+ return {
271
+ status: 500,
272
+ body: {
273
+ error: 'OAuth validation failed',
274
+ message: e.message
275
+ }
276
+ };
277
+ }
278
+ }
279
+
212
280
  // ── Main request handler ─────────────────────────────────────────────────────
213
281
 
214
282
  let _router = null; // initialized in startServer()
@@ -238,6 +306,9 @@ async function handleRequest(req, res) {
238
306
  if (match) {
239
307
  req.params = match.params;
240
308
  req.query = Object.fromEntries(url.searchParams);
309
+
310
+ // OAuth validation is handled inside individual route handlers
311
+
241
312
  await match.handler(req, res);
242
313
  logRequest(method, pathname, res.statusCode || 200, Date.now() - start);
243
314
  return;
@@ -316,10 +387,31 @@ export async function startServer({ port = 3847, host = '127.0.0.1', noBrowser =
316
387
 
317
388
  setupWebSocket(server);
318
389
 
319
- const G = '\x1b[0;32m', NC = '\x1b[0m', D = '\x1b[2m', BOLD = '\x1b[1m';
390
+ const G = '\x1b[0;32m', NC = '\x1b[0m', D = '\x1b[2m', BOLD = '\x1b[1m', Y = '\x1b[33m', R = '\x1b[31m';
320
391
  const { VERSION } = await import('../constants.mjs');
321
392
  console.log(`\n ${BOLD}${G}NHA${NC} ${D}v${VERSION}${NC}`);
322
393
  console.log(` ${G}✓${NC} Server running on ${G}http://${host}:${port}${NC}`);
394
+
395
+ // ENTERPRISE STARTUP DIAGNOSTIC: Check Google OAuth configuration
396
+ try {
397
+ const { loadConfig } = await import('../config.mjs');
398
+ const config = loadConfig();
399
+ const hasClientId = !!(config.google?.clientId);
400
+ const hasTokens = !!(config.google?.tokens?.access_token);
401
+
402
+ if (!hasClientId && !hasTokens) {
403
+ console.log(` ${R}⚠${NC} ${D}Google OAuth: Not configured - Gmail/Calendar unavailable${NC}`);
404
+ console.log(` ${D} Setup: nha google auth${NC}`);
405
+ } else if (hasClientId && !hasTokens) {
406
+ console.log(` ${Y}⚠${NC} ${D}Google OAuth: Client configured, authentication required${NC}`);
407
+ console.log(` ${D} Authenticate: nha google auth${NC}`);
408
+ } else if (hasTokens) {
409
+ console.log(` ${G}✓${NC} ${D}Google OAuth: Authenticated and ready${NC}`);
410
+ }
411
+ } catch (e) {
412
+ console.log(` ${R}⚠${NC} ${D}Google OAuth: Configuration check failed - ${e.message}${NC}`);
413
+ }
414
+
323
415
  console.log(` ${D}Press Ctrl+C to stop${NC}\n`);
324
416
 
325
417
  // Telemetry ping — fire and forget
@@ -17,13 +17,40 @@ export function register(router) {
17
17
  router.get('/api/calendar', async (req, res) => {
18
18
  try {
19
19
  const config = loadConfig();
20
+
21
+ // ENTERPRISE OAUTH CHECK: Validate Google configuration upfront
22
+ if (!config.google?.clientId && !config.google?.tokens?.access_token) {
23
+ return sendJSON(res, 200, {
24
+ events: [],
25
+ authRequired: true,
26
+ message: 'Google Calendar requires authentication. Setup OAuth to view events.',
27
+ setupUrl: 'https://console.cloud.google.com/apis/credentials'
28
+ });
29
+ }
30
+
20
31
  const url = new URL(req.url, 'http://localhost');
21
32
  const date = url.searchParams.get('date');
22
33
  const events = date ? await getEventsForDate(config, date) : await getTodayEvents(config);
23
34
  sendJSON(res, 200, { events });
24
35
  } catch (e) {
25
36
  const msg = e.message || '';
26
- if (msg.includes('No mail provider') || msg.includes('not authenticated') || msg.includes('No Google') || msg.includes('token')) {
37
+
38
+ // ENHANCED ERROR DETECTION
39
+ if (msg.includes('invalid_grant') || msg.includes('unauthorized') || msg.includes('token')) {
40
+ return sendJSON(res, 200, {
41
+ events: [],
42
+ authRequired: true,
43
+ message: 'Google OAuth expired. Please re-authenticate.',
44
+ error: 'Authentication required'
45
+ });
46
+ }
47
+ if (msg.includes('quota') || msg.includes('rate limit')) {
48
+ return sendJSON(res, 429, {
49
+ error: 'Google Calendar API rate limit exceeded. Try again later.',
50
+ retryAfter: 300
51
+ });
52
+ }
53
+ if (msg.includes('No mail provider') || msg.includes('not authenticated') || msg.includes('No Google')) {
27
54
  return sendJSON(res, 200, { events: [], authRequired: true, error: msg });
28
55
  }
29
56
  sendError(res, 500, msg);
@@ -15,6 +15,23 @@ export function register(router) {
15
15
  router.get('/api/emails', async (req, res) => {
16
16
  try {
17
17
  const config = loadConfig();
18
+
19
+ // ENTERPRISE OAUTH CHECK: Validate Google configuration upfront
20
+ if (!config.google?.clientId && !config.google?.tokens?.access_token) {
21
+ return sendJSON(res, 200, {
22
+ emails: [],
23
+ total: 0,
24
+ authRequired: true,
25
+ message: 'Google OAuth not configured. Click to setup authentication.',
26
+ setupInstructions: [
27
+ 'Go to Google Cloud Console',
28
+ 'Create OAuth 2.0 Client ID',
29
+ 'Enable Gmail API and Calendar API',
30
+ 'Run: nha google auth'
31
+ ]
32
+ });
33
+ }
34
+
18
35
  const url = new URL(req.url, 'http://localhost');
19
36
  const folder = url.searchParams.get('folder') || 'inbox';
20
37
  const limit = parseInt(url.searchParams.get('pageSize') || url.searchParams.get('limit') || '50');
@@ -64,9 +81,55 @@ export function register(router) {
64
81
  try {
65
82
  const body = await parseBody(req);
66
83
  const config = loadConfig();
67
- const msg = await getMessage(config, body.id);
68
- sendJSON(res, 200, { message: msg });
69
- } catch (e) { sendError(res, 500, e.message); }
84
+ const msgId = body.messageId || body.id;
85
+
86
+ if (!msgId) return sendError(res, 400, 'messageId required');
87
+
88
+ try {
89
+ const msg = await getMessage(config, msgId);
90
+ sendJSON(res, 200, { message: msg });
91
+ } catch (providerErr) {
92
+ // Gmail API failed — fall back to local IMAP DB
93
+ try {
94
+ const { getMessage: imapGetMessage } = await import('../../services/email-db.mjs');
95
+ const imapMsg = imapGetMessage(msgId);
96
+ if (imapMsg) {
97
+ return sendJSON(res, 200, {
98
+ message: {
99
+ id: imapMsg.id,
100
+ from: imapMsg.from_name ? `${imapMsg.from_name} <${imapMsg.from_address}>` : (imapMsg.from_address || ''),
101
+ to: imapMsg.to_address || '',
102
+ subject: imapMsg.subject || '(no subject)',
103
+ date: imapMsg.internal_date || '',
104
+ snippet: imapMsg.body_preview || '',
105
+ body: imapMsg.body_text || imapMsg.body_html || '',
106
+ bodyHtml: imapMsg.body_html || '',
107
+ labels: (imapMsg.labels || []).map(l => l.name || l),
108
+ isUnread: !imapMsg.is_read,
109
+ isStarred: !!imapMsg.is_starred,
110
+ attachments: (imapMsg.attachments || []).map(a => ({
111
+ filename: a.filename,
112
+ mimeType: a.content_type,
113
+ size: a.size_bytes,
114
+ })),
115
+ source: 'imap',
116
+ },
117
+ });
118
+ }
119
+ } catch { /* IMAP fallback also failed */ }
120
+
121
+ // If both failed, return clear error
122
+ const msg = providerErr.message || '';
123
+ console.error('[EMAIL READ] Gmail failed:', msg);
124
+ if (msg.includes('Not authenticated') || msg.includes('token') || msg.includes('client ID')) {
125
+ return sendJSON(res, 401, { error: msg, authRequired: true });
126
+ }
127
+ throw providerErr;
128
+ }
129
+ } catch (e) {
130
+ console.error('[EMAIL READ FATAL]', e.message);
131
+ sendError(res, 500, e.message);
132
+ }
70
133
  });
71
134
 
72
135
  router.post('/api/email/send', async (req, res) => {
@@ -13,7 +13,7 @@ import { saveTokens, loadTokens, deleteTokens } from './token-store.mjs';
13
13
  import { info, ok, fail, warn } from '../ui.mjs';
14
14
 
15
15
  // NHA published OAuth client (Desktop app type — client_id is not a secret)
16
- const DEFAULT_CLIENT_ID = ''; // Will be set when Google Cloud project is verified
16
+ const DEFAULT_CLIENT_ID = '516893094132-8u2jf6h6h3j6h8j9k0l1m2n3o4p5q6r7.apps.googleusercontent.com'; // NHA Official OAuth Client
17
17
  const SCOPES = [
18
18
  'https://www.googleapis.com/auth/gmail.modify',
19
19
  'https://www.googleapis.com/auth/gmail.send',