opencode-remote-control 0.2.7 ā 0.2.8
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/dist/core/auth.js +95 -0
- package/dist/feishu/bot.js +89 -3
- package/dist/telegram/bot.js +73 -1
- package/package.json +1 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Authorization management for OpenCode Remote Control
|
|
2
|
+
// First user to send /start becomes the owner automatically
|
|
3
|
+
const authState = {
|
|
4
|
+
telegramOwner: null,
|
|
5
|
+
feishuOwner: null,
|
|
6
|
+
};
|
|
7
|
+
// Auth file path for persistence
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
11
|
+
const AUTH_DIR = join(homedir(), '.opencode-remote');
|
|
12
|
+
const AUTH_FILE = join(AUTH_DIR, 'auth.json');
|
|
13
|
+
function ensureAuthDir() {
|
|
14
|
+
if (!existsSync(AUTH_DIR)) {
|
|
15
|
+
mkdirSync(AUTH_DIR, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function loadAuth() {
|
|
19
|
+
try {
|
|
20
|
+
if (existsSync(AUTH_FILE)) {
|
|
21
|
+
const data = JSON.parse(readFileSync(AUTH_FILE, 'utf-8'));
|
|
22
|
+
authState.telegramOwner = data.telegramOwner || null;
|
|
23
|
+
authState.feishuOwner = data.feishuOwner || null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
console.warn('Failed to load auth state, starting fresh:', error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function saveAuth() {
|
|
31
|
+
try {
|
|
32
|
+
ensureAuthDir();
|
|
33
|
+
writeFileSync(AUTH_FILE, JSON.stringify(authState, null, 2));
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.error('Failed to save auth state:', error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Initialize on module load
|
|
40
|
+
loadAuth();
|
|
41
|
+
export function isAuthorized(platform, userId) {
|
|
42
|
+
if (platform === 'telegram') {
|
|
43
|
+
return authState.telegramOwner === userId;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
return authState.feishuOwner === userId;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function hasOwner(platform) {
|
|
50
|
+
if (platform === 'telegram') {
|
|
51
|
+
return authState.telegramOwner !== null;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
return authState.feishuOwner !== null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function claimOwnership(platform, userId) {
|
|
58
|
+
if (platform === 'telegram') {
|
|
59
|
+
if (authState.telegramOwner) {
|
|
60
|
+
if (authState.telegramOwner === userId) {
|
|
61
|
+
return { success: true, message: 'already_owner' };
|
|
62
|
+
}
|
|
63
|
+
return { success: false, message: 'already_claimed' };
|
|
64
|
+
}
|
|
65
|
+
authState.telegramOwner = userId;
|
|
66
|
+
saveAuth();
|
|
67
|
+
return { success: true, message: 'claimed' };
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
if (authState.feishuOwner) {
|
|
71
|
+
if (authState.feishuOwner === userId) {
|
|
72
|
+
return { success: true, message: 'already_owner' };
|
|
73
|
+
}
|
|
74
|
+
return { success: false, message: 'already_claimed' };
|
|
75
|
+
}
|
|
76
|
+
authState.feishuOwner = userId;
|
|
77
|
+
saveAuth();
|
|
78
|
+
return { success: true, message: 'claimed' };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export function getOwner(platform) {
|
|
82
|
+
if (platform === 'telegram') {
|
|
83
|
+
return authState.telegramOwner;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
return authState.feishuOwner;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// For debugging/display
|
|
90
|
+
export function getAuthStatus() {
|
|
91
|
+
return {
|
|
92
|
+
telegram: authState.telegramOwner !== null,
|
|
93
|
+
feishu: authState.feishuOwner !== null,
|
|
94
|
+
};
|
|
95
|
+
}
|
package/dist/feishu/bot.js
CHANGED
|
@@ -5,6 +5,7 @@ import { initSessionManager, getOrCreateSession } from '../core/session.js';
|
|
|
5
5
|
import { splitMessage } from '../core/notifications.js';
|
|
6
6
|
import { EMOJI } from '../core/types.js';
|
|
7
7
|
import { initOpenCode, createSession, sendMessage, checkConnection } from '../opencode/client.js';
|
|
8
|
+
import { isAuthorized, hasOwner, claimOwnership, getAuthStatus } from '../core/auth.js';
|
|
8
9
|
let feishuClient = null;
|
|
9
10
|
let wsClient = null;
|
|
10
11
|
let config = null;
|
|
@@ -82,9 +83,33 @@ async function handleCommand(adapter, ctx, text) {
|
|
|
82
83
|
const parts = text.split(/\s+/);
|
|
83
84
|
const command = parts[0].toLowerCase();
|
|
84
85
|
switch (command) {
|
|
85
|
-
case '/start':
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
case '/start': {
|
|
87
|
+
const result = claimOwnership('feishu', ctx.userId);
|
|
88
|
+
if (result.success) {
|
|
89
|
+
if (result.message === 'claimed') {
|
|
90
|
+
await adapter.reply(ctx.threadId, `š **Security Setup Complete!**
|
|
91
|
+
|
|
92
|
+
ā
You are now the authorized owner of this bot.
|
|
93
|
+
|
|
94
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
95
|
+
ā ļø **IMPORTANT SECURITY NOTICE**
|
|
96
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
97
|
+
|
|
98
|
+
Only YOU can control OpenCode through this bot.
|
|
99
|
+
Other users will be blocked automatically.
|
|
100
|
+
|
|
101
|
+
Your Feishu ID: \`${ctx.userId}\`
|
|
102
|
+
|
|
103
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
104
|
+
|
|
105
|
+
š **Ready to use!**
|
|
106
|
+
š¬ Send me a prompt to start coding
|
|
107
|
+
/help ā see all commands
|
|
108
|
+
/status ā check OpenCode connection`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Already owner
|
|
112
|
+
await adapter.reply(ctx.threadId, `š OpenCode Remote Control ready
|
|
88
113
|
|
|
89
114
|
š¬ Send me a prompt to start coding
|
|
90
115
|
/help ā see all commands
|
|
@@ -100,6 +125,31 @@ Commands:
|
|
|
100
125
|
/files ā List changed files
|
|
101
126
|
/retry ā Retry connection
|
|
102
127
|
|
|
128
|
+
š¬ Anything else is treated as a prompt for OpenCode!`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Already claimed by someone else
|
|
133
|
+
await adapter.reply(ctx.threadId, `š« **Access Denied**
|
|
134
|
+
|
|
135
|
+
This bot is already secured by another user.
|
|
136
|
+
|
|
137
|
+
If you are the owner, check your configuration.`);
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case '/help':
|
|
142
|
+
await adapter.reply(ctx.threadId, `š Commands
|
|
143
|
+
|
|
144
|
+
/start ā Claim ownership & Start bot
|
|
145
|
+
/status ā Check connection
|
|
146
|
+
/reset ā Reset session
|
|
147
|
+
/approve ā Approve pending changes
|
|
148
|
+
/reject ā Reject pending changes
|
|
149
|
+
/diff ā See full diff
|
|
150
|
+
/files ā List changed files
|
|
151
|
+
/retry ā Retry connection
|
|
152
|
+
|
|
103
153
|
š¬ Anything else is treated as a prompt for OpenCode!`);
|
|
104
154
|
break;
|
|
105
155
|
case '/approve': {
|
|
@@ -180,9 +230,26 @@ async function handleMessage(adapter, ctx, text) {
|
|
|
180
230
|
const session = getOrCreateSession(ctx.threadId, 'feishu');
|
|
181
231
|
// Check if it's a command
|
|
182
232
|
if (text.startsWith('/')) {
|
|
233
|
+
// Commands are handled by handleCommand which has its own auth logic for /start
|
|
183
234
|
await handleCommand(adapter, ctx, text);
|
|
184
235
|
return;
|
|
185
236
|
}
|
|
237
|
+
// Authorization check for non-command messages
|
|
238
|
+
if (!isAuthorized('feishu', ctx.userId)) {
|
|
239
|
+
if (!hasOwner('feishu')) {
|
|
240
|
+
await adapter.reply(ctx.threadId, `š **Authorization Required**
|
|
241
|
+
|
|
242
|
+
This bot is not yet secured.
|
|
243
|
+
|
|
244
|
+
Please send /start to claim ownership first.`);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
await adapter.reply(ctx.threadId, `š« **Access Denied**
|
|
248
|
+
|
|
249
|
+
You are not authorized to use this bot.`);
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
186
253
|
// Check OpenCode connection
|
|
187
254
|
const connected = await checkConnection();
|
|
188
255
|
if (!connected) {
|
|
@@ -347,6 +414,25 @@ export async function startFeishuBot(botConfig) {
|
|
|
347
414
|
console.log('⨠Long connection mode - NO tunnel/ngrok required!');
|
|
348
415
|
console.log(' Just make sure your computer can access the internet.');
|
|
349
416
|
console.log('');
|
|
417
|
+
// Show security status
|
|
418
|
+
const authStatus = getAuthStatus();
|
|
419
|
+
if (!authStatus.feishu) {
|
|
420
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
421
|
+
console.log(' š SECURITY NOTICE');
|
|
422
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
423
|
+
console.log('');
|
|
424
|
+
console.log(' Bot is NOT yet secured!');
|
|
425
|
+
console.log(' The FIRST user to send /start will become the owner.');
|
|
426
|
+
console.log('');
|
|
427
|
+
console.log(' š Open Feishu and send /start to YOUR bot NOW!');
|
|
428
|
+
console.log('');
|
|
429
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
430
|
+
console.log('');
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
console.log('š Bot is secured (owner authorized)');
|
|
434
|
+
console.log('');
|
|
435
|
+
}
|
|
350
436
|
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
351
437
|
console.log(' š Configuration Checklist');
|
|
352
438
|
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
package/dist/telegram/bot.js
CHANGED
|
@@ -4,6 +4,7 @@ import { loadConfig } from '../core/types.js';
|
|
|
4
4
|
import { initSessionManager, getOrCreateSession } from '../core/session.js';
|
|
5
5
|
import { splitMessage } from '../core/notifications.js';
|
|
6
6
|
import { initOpenCode, createSession, sendMessage, checkConnection } from '../opencode/client.js';
|
|
7
|
+
import { isAuthorized, hasOwner, claimOwnership, getAuthStatus } from '../core/auth.js';
|
|
7
8
|
// Lazy initialization - bot is only created when startBot() is called
|
|
8
9
|
let config = null;
|
|
9
10
|
let bot = null;
|
|
@@ -18,11 +19,47 @@ function getThreadId(ctx) {
|
|
|
18
19
|
function setupBotCommands(bot, openCodeSessions) {
|
|
19
20
|
// Start command
|
|
20
21
|
bot.command('start', async (ctx) => {
|
|
21
|
-
|
|
22
|
+
const userId = String(ctx.from?.id);
|
|
23
|
+
const result = claimOwnership('telegram', userId);
|
|
24
|
+
if (result.success) {
|
|
25
|
+
if (result.message === 'claimed') {
|
|
26
|
+
await ctx.reply(`š **Security Setup Complete!**
|
|
27
|
+
|
|
28
|
+
ā
You are now the authorized owner of this bot.
|
|
29
|
+
|
|
30
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
31
|
+
ā ļø **IMPORTANT SECURITY NOTICE**
|
|
32
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
33
|
+
|
|
34
|
+
Only YOU can control OpenCode through this bot.
|
|
35
|
+
Other users will be blocked automatically.
|
|
36
|
+
|
|
37
|
+
Your Telegram ID: \`${userId}\`
|
|
38
|
+
|
|
39
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
40
|
+
|
|
41
|
+
š **Ready to use!**
|
|
42
|
+
š¬ Send me a prompt to start coding
|
|
43
|
+
/help ā see all commands
|
|
44
|
+
/status ā check OpenCode connection`, { parse_mode: 'Markdown' });
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Already owner
|
|
48
|
+
await ctx.reply(`š OpenCode Remote Control ready
|
|
22
49
|
|
|
23
50
|
š¬ Send me a prompt to start coding
|
|
24
51
|
/help ā see all commands
|
|
25
52
|
/status ā check OpenCode connection`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// Already claimed by someone else
|
|
57
|
+
await ctx.reply(`š« **Access Denied**
|
|
58
|
+
|
|
59
|
+
This bot is already secured by another user.
|
|
60
|
+
|
|
61
|
+
If you are the owner, check your configuration.`, { parse_mode: 'Markdown' });
|
|
62
|
+
}
|
|
26
63
|
});
|
|
27
64
|
// Help command
|
|
28
65
|
bot.command('help', async (ctx) => {
|
|
@@ -132,9 +169,26 @@ Cannot connect to OpenCode server.
|
|
|
132
169
|
// Handle all other messages as prompts
|
|
133
170
|
bot.on('message:text', async (ctx) => {
|
|
134
171
|
const text = ctx.message.text;
|
|
172
|
+
const userId = String(ctx.from?.id);
|
|
135
173
|
// Skip if it's a command (already handled)
|
|
136
174
|
if (text.startsWith('/'))
|
|
137
175
|
return;
|
|
176
|
+
// Authorization check
|
|
177
|
+
if (!isAuthorized('telegram', userId)) {
|
|
178
|
+
if (!hasOwner('telegram')) {
|
|
179
|
+
await ctx.reply(`š **Authorization Required**
|
|
180
|
+
|
|
181
|
+
This bot is not yet secured.
|
|
182
|
+
|
|
183
|
+
Please send /start to claim ownership first.`, { parse_mode: 'Markdown' });
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
await ctx.reply(`š« **Access Denied**
|
|
187
|
+
|
|
188
|
+
You are not authorized to use this bot.`, { parse_mode: 'Markdown' });
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
138
192
|
const threadId = getThreadId(ctx);
|
|
139
193
|
// Send typing indicator
|
|
140
194
|
await ctx.api.sendChatAction(ctx.chat.id, 'typing');
|
|
@@ -236,6 +290,24 @@ export async function startBot() {
|
|
|
236
290
|
console.log('Make sure OpenCode is running');
|
|
237
291
|
}
|
|
238
292
|
console.log('š Starting Telegram bot...');
|
|
293
|
+
// Show security status
|
|
294
|
+
const authStatus = getAuthStatus();
|
|
295
|
+
if (!authStatus.telegram) {
|
|
296
|
+
console.log('');
|
|
297
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
298
|
+
console.log(' š SECURITY NOTICE');
|
|
299
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
300
|
+
console.log('');
|
|
301
|
+
console.log(' Bot is NOT yet secured!');
|
|
302
|
+
console.log(' The FIRST user to send /start will become the owner.');
|
|
303
|
+
console.log('');
|
|
304
|
+
console.log(' š Open Telegram and send /start to YOUR bot NOW!');
|
|
305
|
+
console.log('');
|
|
306
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
console.log('š Bot is secured (owner authorized)');
|
|
310
|
+
}
|
|
239
311
|
// Handle graceful shutdown
|
|
240
312
|
const shutdown = async () => {
|
|
241
313
|
console.log('\nš Shutting down Telegram bot...');
|