nothumanallowed 13.5.163 → 13.5.164
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 +1 -1
- package/src/commands/ui.mjs +39 -7
- package/src/constants.mjs +1 -1
- package/src/services/google-oauth.mjs +46 -0
- package/src/services/web-ui.mjs +8 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.5.
|
|
3
|
+
"version": "13.5.164",
|
|
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/commands/ui.mjs
CHANGED
|
@@ -60,6 +60,9 @@ import {
|
|
|
60
60
|
|
|
61
61
|
const DEFAULT_PORT = 3847;
|
|
62
62
|
|
|
63
|
+
// In-memory pending OAuth state (verifier + state for PKCE callback)
|
|
64
|
+
let _googleOAuthPending = null;
|
|
65
|
+
|
|
63
66
|
/**
|
|
64
67
|
* Extract text from PDF buffer — zero dependencies.
|
|
65
68
|
* Handles text-based PDFs (not scanned images).
|
|
@@ -361,15 +364,18 @@ export async function cmdUI(args) {
|
|
|
361
364
|
return;
|
|
362
365
|
}
|
|
363
366
|
|
|
364
|
-
// POST /api/google/auth —
|
|
367
|
+
// POST /api/google/auth — return OAuth URL for the browser to open
|
|
365
368
|
if (method === 'POST' && pathname === '/api/google/auth') {
|
|
366
369
|
try {
|
|
367
|
-
const {
|
|
368
|
-
//
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
370
|
+
const { buildAuthUrl } = await import('../services/google-oauth.mjs');
|
|
371
|
+
// Derive the redirect URI from the request Host header so it works
|
|
372
|
+
// on any IP/port (localhost, LAN, VM, etc.)
|
|
373
|
+
const host = req.headers['host'] || `127.0.0.1:${PORT}`;
|
|
374
|
+
const redirectUri = `http://${host}/api/google/callback`;
|
|
375
|
+
const { url, verifier, state } = buildAuthUrl(config, redirectUri);
|
|
376
|
+
// Store verifier+state in memory for the callback
|
|
377
|
+
_googleOAuthPending = { verifier, state, redirectUri };
|
|
378
|
+
sendJSON(res, 200, { ok: true, url });
|
|
373
379
|
} catch (e) {
|
|
374
380
|
sendJSON(res, 500, { error: e.message });
|
|
375
381
|
}
|
|
@@ -377,6 +383,32 @@ export async function cmdUI(args) {
|
|
|
377
383
|
return;
|
|
378
384
|
}
|
|
379
385
|
|
|
386
|
+
// GET /api/google/callback — receives OAuth code from Google redirect
|
|
387
|
+
if (method === 'GET' && pathname === '/api/google/callback') {
|
|
388
|
+
const params = new URL(req.url, `http://localhost`).searchParams;
|
|
389
|
+
const code = params.get('code');
|
|
390
|
+
const state = params.get('state');
|
|
391
|
+
if (!code || !_googleOAuthPending || _googleOAuthPending.state !== state) {
|
|
392
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
393
|
+
res.end('<html><body><h2>OAuth error: invalid state or missing code.</h2><p>Please try again from the NHA UI.</p></body></html>');
|
|
394
|
+
logRequest(method, pathname, 400, Date.now() - start);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
const { exchangeCodeFromUI } = await import('../services/google-oauth.mjs');
|
|
399
|
+
const { email } = await exchangeCodeFromUI(config, code, _googleOAuthPending.verifier, _googleOAuthPending.redirectUri);
|
|
400
|
+
_googleOAuthPending = null;
|
|
401
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
402
|
+
res.end(`<html><body style="font-family:sans-serif;text-align:center;padding:60px;background:#0a0a0a;color:#fff"><h2 style="color:#22c55e">✓ Google Connected!</h2><p style="color:#aaa">Signed in as <strong>${email}</strong></p><p style="color:#aaa">You can close this tab and return to NHA.</p><script>setTimeout(function(){window.close()},3000)</script></body></html>`);
|
|
403
|
+
} catch (e) {
|
|
404
|
+
_googleOAuthPending = null;
|
|
405
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
406
|
+
res.end(`<html><body style="font-family:sans-serif;text-align:center;padding:60px;background:#0a0a0a;color:#fff"><h2 style="color:#ef4444">✗ Error</h2><p style="color:#aaa">${e.message}</p></body></html>`);
|
|
407
|
+
}
|
|
408
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
380
412
|
// ── Collab (Alexandria proxy) ─────────────────────────────────────
|
|
381
413
|
if (pathname.startsWith('/api/collab/')) {
|
|
382
414
|
const collabAction = pathname.split('/').pop();
|
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 = '13.5.
|
|
8
|
+
export const VERSION = '13.5.164';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -305,6 +305,52 @@ export async function runAuthFlow(config, manual = false) {
|
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Build an OAuth URL for the web UI flow.
|
|
310
|
+
* The redirect_uri points back to the NHA web UI server so the callback
|
|
311
|
+
* is received directly in the browser session (works across VMs/headless).
|
|
312
|
+
*
|
|
313
|
+
* @param {object} config
|
|
314
|
+
* @param {string} redirectUri — e.g. http://192.168.1.45:3847/api/google/callback
|
|
315
|
+
* @returns {{ url: string, verifier: string, state: string }}
|
|
316
|
+
*/
|
|
317
|
+
export function buildAuthUrl(config, redirectUri) {
|
|
318
|
+
const clientId = config.google?.clientId || DEFAULT_CLIENT_ID;
|
|
319
|
+
if (!clientId) throw new Error('Google client ID not configured. Run: nha config set google-client-id YOUR_ID');
|
|
320
|
+
const { verifier, challenge } = generatePKCE();
|
|
321
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
322
|
+
const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');
|
|
323
|
+
authUrl.searchParams.set('client_id', clientId);
|
|
324
|
+
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
325
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
326
|
+
authUrl.searchParams.set('scope', SCOPES);
|
|
327
|
+
authUrl.searchParams.set('state', state);
|
|
328
|
+
authUrl.searchParams.set('code_challenge', challenge);
|
|
329
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
330
|
+
authUrl.searchParams.set('access_type', 'offline');
|
|
331
|
+
authUrl.searchParams.set('prompt', 'consent');
|
|
332
|
+
return { url: authUrl.toString(), verifier, state };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Exchange an auth code received by the web UI callback.
|
|
337
|
+
*/
|
|
338
|
+
export async function exchangeCodeFromUI(config, code, verifier, redirectUri) {
|
|
339
|
+
const clientId = config.google?.clientId || DEFAULT_CLIENT_ID;
|
|
340
|
+
const clientSecret = config.google?.clientSecret || '';
|
|
341
|
+
const tokenData = await exchangeCode(code, verifier, clientId, clientSecret, redirectUri);
|
|
342
|
+
const email = await getUserEmail(tokenData.access_token);
|
|
343
|
+
const tokens = {
|
|
344
|
+
access_token: tokenData.access_token,
|
|
345
|
+
refresh_token: tokenData.refresh_token,
|
|
346
|
+
expires_at: Date.now() + (tokenData.expires_in * 1000),
|
|
347
|
+
scope: tokenData.scope,
|
|
348
|
+
email: email || 'unknown',
|
|
349
|
+
};
|
|
350
|
+
saveTokens(tokens);
|
|
351
|
+
return { email: email || 'unknown' };
|
|
352
|
+
}
|
|
353
|
+
|
|
308
354
|
/**
|
|
309
355
|
* Show connection status.
|
|
310
356
|
*/
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -3405,10 +3405,15 @@ function imapSync(accountId) {
|
|
|
3405
3405
|
|
|
3406
3406
|
function connectGoogle() {
|
|
3407
3407
|
var s = document.getElementById('googleStatus');
|
|
3408
|
-
if (s) s.textContent = '
|
|
3408
|
+
if (s) { s.textContent = 'Opening Google sign-in...'; s.style.color = 'var(--dim)'; }
|
|
3409
3409
|
apiPost('/api/google/auth', {}).then(function(r) {
|
|
3410
|
-
if (
|
|
3411
|
-
|
|
3410
|
+
if (r.url) {
|
|
3411
|
+
// Open OAuth URL in current browser — works on VMs and LAN
|
|
3412
|
+
window.open(r.url, '_blank');
|
|
3413
|
+
if (s) { s.textContent = 'Sign-in page opened. Complete the login then reload NHA.'; s.style.color = 'var(--green)'; }
|
|
3414
|
+
} else if (r.error) {
|
|
3415
|
+
if (s) { s.textContent = 'Error: ' + r.error; s.style.color = 'var(--red)'; }
|
|
3416
|
+
}
|
|
3412
3417
|
}).catch(function(e) {
|
|
3413
3418
|
if (s) { s.textContent = 'Error: ' + e.message; s.style.color = 'var(--red)'; }
|
|
3414
3419
|
});
|