troxy-cli 1.2.8 → 1.3.1
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/api.js +6 -3
- package/src/auth.js +40 -16
- package/src/policies.js +10 -6
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -24,9 +24,12 @@ async function request(method, path, { apiKey, jwt, body } = {}) {
|
|
|
24
24
|
|
|
25
25
|
export const api = {
|
|
26
26
|
// Auth
|
|
27
|
-
health:
|
|
28
|
-
magicLink:
|
|
29
|
-
verify:
|
|
27
|
+
health: () => request('GET', '/health'),
|
|
28
|
+
magicLink: (email) => request('POST', '/auth/magic-link', { body: { email } }),
|
|
29
|
+
verify: (token) => request('POST', '/auth/verify', { body: { token } }),
|
|
30
|
+
cliStart: () => request('POST', '/auth/cli/start', {}),
|
|
31
|
+
cliAuthorize: (jwt, session_id) => request('POST', '/auth/cli/authorize', { jwt, body: { session_id } }),
|
|
32
|
+
cliExchange: (session_id, code) => request('POST', '/auth/cli/exchange', { body: { session_id, code } }),
|
|
30
33
|
|
|
31
34
|
// Cards
|
|
32
35
|
listCards: (jwt) => request('GET', '/cards', { jwt }),
|
package/src/auth.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import readline from 'readline';
|
|
5
|
+
import { exec } from 'child_process';
|
|
5
6
|
import { api } from './api.js';
|
|
6
7
|
|
|
7
8
|
const SESSION_FILE = path.join(os.homedir(), '.troxy', 'session.json');
|
|
@@ -66,34 +67,57 @@ function loadConfig() {
|
|
|
66
67
|
try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; }
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
70
|
+
function _openBrowser(url) {
|
|
71
|
+
const isHeadless = process.platform === 'linux'
|
|
72
|
+
&& !process.env.DISPLAY
|
|
73
|
+
&& !process.env.WAYLAND_DISPLAY;
|
|
74
|
+
if (isHeadless) return;
|
|
75
|
+
const cmd = process.platform === 'darwin' ? `open "${url}"`
|
|
76
|
+
: process.platform === 'win32' ? `start "" "${url}"`
|
|
77
|
+
: `xdg-open "${url}"`;
|
|
78
|
+
exec(cmd, () => {});
|
|
79
|
+
}
|
|
75
80
|
|
|
76
|
-
|
|
81
|
+
/** Device-code login flow — opens browser, user copies code back to CLI. */
|
|
82
|
+
export async function runLogin() {
|
|
83
|
+
// 1. Start a CLI auth session
|
|
84
|
+
let session;
|
|
77
85
|
try {
|
|
78
|
-
await api.
|
|
79
|
-
console.log('✓');
|
|
86
|
+
session = await api.cliStart();
|
|
80
87
|
} catch (err) {
|
|
81
|
-
console.
|
|
82
|
-
console.error(` Error: ${err.message}\n`);
|
|
88
|
+
console.error(`\n Error starting login: ${err.message}\n`);
|
|
83
89
|
process.exit(1);
|
|
84
90
|
}
|
|
85
91
|
|
|
92
|
+
// 2. Open browser (or print URL on headless servers)
|
|
93
|
+
const isHeadless = process.platform === 'linux' && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY;
|
|
94
|
+
if (isHeadless) {
|
|
95
|
+
console.log('\n Open this URL in your browser to get a login code:\n');
|
|
96
|
+
console.log(` ${session.url}\n`);
|
|
97
|
+
} else {
|
|
98
|
+
console.log('\n Opening browser to complete login...');
|
|
99
|
+
console.log(` If it didn't open, visit:\n ${session.url}\n`);
|
|
100
|
+
_openBrowser(session.url);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 3. Prompt for the code shown in the browser
|
|
86
104
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
87
|
-
const
|
|
88
|
-
rl.question('
|
|
105
|
+
const code = await new Promise(resolve =>
|
|
106
|
+
rl.question(' Paste the code from your browser: ', ans => { rl.close(); resolve(ans.trim()); })
|
|
89
107
|
);
|
|
90
108
|
|
|
109
|
+
if (!code) {
|
|
110
|
+
console.error('\n No code entered. Run troxy login to try again.\n');
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 4. Exchange code for JWT
|
|
91
115
|
process.stdout.write(' Verifying... ');
|
|
92
116
|
try {
|
|
93
|
-
const result = await api.
|
|
94
|
-
saveSession({ jwt: result.access_token, email });
|
|
117
|
+
const result = await api.cliExchange(session.session_id, code);
|
|
118
|
+
saveSession({ jwt: result.access_token, email: result.email });
|
|
95
119
|
console.log('✓');
|
|
96
|
-
console.log(`\n Logged in as ${email}\n`);
|
|
120
|
+
console.log(`\n Logged in as ${result.email} (session valid for 12 hours)\n`);
|
|
97
121
|
} catch (err) {
|
|
98
122
|
console.log('✗');
|
|
99
123
|
console.error(` Error: ${err.message}\n`);
|
package/src/policies.js
CHANGED
|
@@ -126,9 +126,11 @@ export async function runPolicies([sub, ...args], flags) {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
const _isAny = x => !x.field || x.field === 'any' || x.operator === 'any';
|
|
130
|
+
|
|
129
131
|
function _condSummary(p) {
|
|
130
|
-
const c = p.conditions || [];
|
|
131
|
-
const o = p.or_conditions || [];
|
|
132
|
+
const c = (p.conditions || []).filter(x => !_isAny(x));
|
|
133
|
+
const o = (p.or_conditions || []).filter(row => (row.conditions || []).some(x => !_isAny(x)));
|
|
132
134
|
const total = c.length + o.length;
|
|
133
135
|
if (total === 0) return 'always';
|
|
134
136
|
return `${total} condition${total > 1 ? 's' : ''}`;
|
|
@@ -138,13 +140,15 @@ function _condDetail(p) {
|
|
|
138
140
|
const c = p.conditions || [];
|
|
139
141
|
const or = p.or_conditions || [];
|
|
140
142
|
const parts = [];
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
const real = c.filter(x => !_isAny(x));
|
|
144
|
+
if (real.length) {
|
|
145
|
+
parts.push(real.map(x => `${x.field} ${x.operator} ${x.value || ''}${x.value2 ? '–'+x.value2 : ''}`).join(' AND '));
|
|
143
146
|
}
|
|
144
147
|
if (or.length) {
|
|
145
148
|
or.forEach(row => {
|
|
146
|
-
const
|
|
147
|
-
|
|
149
|
+
const realConds = (row.conditions || []).filter(x => !_isAny(x));
|
|
150
|
+
const conds = realConds.map(x => `${x.field} ${x.operator} ${x.value || ''}`).join(' AND ');
|
|
151
|
+
parts.push(`${row.action || ''}${conds ? ' if ' + conds : ''}`);
|
|
148
152
|
});
|
|
149
153
|
}
|
|
150
154
|
return parts.length ? parts.join('\n ') : 'none (always matches)';
|