get-claudia 1.10.2 → 1.10.4
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/gateway/scripts/install.ps1 +27 -1
- package/gateway/scripts/install.sh +39 -9
- package/gateway/src/index.js +25 -1
- package/gateway/src/router.js +24 -0
- package/gateway/src/utils/auth.js +34 -6
- package/gateway/tests/auth.test.js +46 -0
- package/package.json +1 -1
|
@@ -352,7 +352,24 @@ if ($showGuide -match "^[Yy]") {
|
|
|
352
352
|
Write-Host " (or open: ${DIM}https://t.me/userinfobot${NC})"
|
|
353
353
|
Write-Host " Send it any message. It replies with your ID (a number)."
|
|
354
354
|
Write-Host ""
|
|
355
|
-
|
|
355
|
+
while ($true) {
|
|
356
|
+
$userId = Read-Host " Paste your user ID here (or press Enter to skip)"
|
|
357
|
+
|
|
358
|
+
# Empty = skip
|
|
359
|
+
if (-not $userId) { break }
|
|
360
|
+
|
|
361
|
+
# Strip leading @
|
|
362
|
+
$userId = $userId -replace '^@', ''
|
|
363
|
+
|
|
364
|
+
# Validate: must be all digits
|
|
365
|
+
if ($userId -match '^\d+$') { break }
|
|
366
|
+
|
|
367
|
+
Write-Host ""
|
|
368
|
+
Write-Host " ${RED}✗${NC} That looks like a username, not a numeric ID."
|
|
369
|
+
Write-Host " Telegram user IDs are numbers only (e.g. ${BOLD}1588190837${NC})."
|
|
370
|
+
Write-Host " Get yours from ${BOLD}@userinfobot${NC} in Telegram."
|
|
371
|
+
Write-Host ""
|
|
372
|
+
}
|
|
356
373
|
|
|
357
374
|
if ($userId) {
|
|
358
375
|
# Write the config
|
|
@@ -392,6 +409,15 @@ if ($showGuide -match "^[Yy]") {
|
|
|
392
409
|
Add-Content -Path $profilePath -Value "`n# Claudia Gateway - Telegram`n$tokenLine"
|
|
393
410
|
Write-Host " ${GREEN}✓${NC} Bot token saved to PowerShell profile"
|
|
394
411
|
}
|
|
412
|
+
|
|
413
|
+
Write-Host ""
|
|
414
|
+
Write-Host " ${YELLOW}┌─────────────────────────────────────────────────┐${NC}"
|
|
415
|
+
Write-Host " ${YELLOW}│${NC} ${BOLD}Open a NEW terminal${NC} to run the gateway. ${YELLOW}│${NC}"
|
|
416
|
+
Write-Host " ${YELLOW}│${NC} This terminal doesn't have your token yet. ${YELLOW}│${NC}"
|
|
417
|
+
Write-Host " ${YELLOW}│${NC} ${YELLOW}│${NC}"
|
|
418
|
+
Write-Host " ${YELLOW}│${NC} Or reload your profile in this terminal: ${YELLOW}│${NC}"
|
|
419
|
+
Write-Host " ${YELLOW}│${NC} ${CYAN}. `$PROFILE${NC} ${YELLOW}│${NC}"
|
|
420
|
+
Write-Host " ${YELLOW}└─────────────────────────────────────────────────┘${NC}"
|
|
395
421
|
} else {
|
|
396
422
|
Write-Host ""
|
|
397
423
|
Write-Host " ${YELLOW}!${NC} Skipped user ID. You'll need to add it manually:"
|
|
@@ -22,6 +22,14 @@ BIN_DIR="$CLAUDIA_DIR/bin"
|
|
|
22
22
|
# Upgrade mode: skip config generation if config exists
|
|
23
23
|
IS_UPGRADE="${CLAUDIA_GATEWAY_UPGRADE:-0}"
|
|
24
24
|
|
|
25
|
+
# Detect shell rc file (used for PATH and token persistence)
|
|
26
|
+
SHELL_RC=""
|
|
27
|
+
if [ -n "$ZSH_VERSION" ] || [ "$(basename "$SHELL")" = "zsh" ]; then
|
|
28
|
+
SHELL_RC="$HOME/.zshrc"
|
|
29
|
+
elif [ -n "$BASH_VERSION" ] || [ "$(basename "$SHELL")" = "bash" ]; then
|
|
30
|
+
SHELL_RC="$HOME/.bashrc"
|
|
31
|
+
fi
|
|
32
|
+
|
|
25
33
|
# Clear screen and show banner
|
|
26
34
|
clear
|
|
27
35
|
|
|
@@ -244,14 +252,6 @@ PATH_ADDED=0
|
|
|
244
252
|
if [[ ":$PATH:" != *":$BIN_DIR:"* ]]; then
|
|
245
253
|
PATH_LINE='export PATH="$HOME/.claudia/bin:$PATH"'
|
|
246
254
|
|
|
247
|
-
# Detect shell rc file
|
|
248
|
-
SHELL_RC=""
|
|
249
|
-
if [ -n "$ZSH_VERSION" ] || [ "$(basename "$SHELL")" = "zsh" ]; then
|
|
250
|
-
SHELL_RC="$HOME/.zshrc"
|
|
251
|
-
elif [ -n "$BASH_VERSION" ] || [ "$(basename "$SHELL")" = "bash" ]; then
|
|
252
|
-
SHELL_RC="$HOME/.bashrc"
|
|
253
|
-
fi
|
|
254
|
-
|
|
255
255
|
if [ -n "$SHELL_RC" ]; then
|
|
256
256
|
# Check if already in rc file (even if not in current PATH)
|
|
257
257
|
if [ -f "$SHELL_RC" ] && grep -q '\.claudia/bin' "$SHELL_RC" 2>/dev/null; then
|
|
@@ -409,7 +409,28 @@ if [[ "$SHOW_GUIDE" =~ ^[Yy] ]]; then
|
|
|
409
409
|
echo -e " (or tap: ${DIM}https://t.me/userinfobot${NC})"
|
|
410
410
|
echo -e " Send it any message. It replies with your ID (a number)."
|
|
411
411
|
echo
|
|
412
|
-
|
|
412
|
+
while true; do
|
|
413
|
+
read -p " Paste your user ID here (or press Enter to skip): " USER_ID
|
|
414
|
+
|
|
415
|
+
# Empty = skip
|
|
416
|
+
if [ -z "$USER_ID" ]; then
|
|
417
|
+
break
|
|
418
|
+
fi
|
|
419
|
+
|
|
420
|
+
# Strip leading @
|
|
421
|
+
USER_ID="${USER_ID#@}"
|
|
422
|
+
|
|
423
|
+
# Validate: must be all digits
|
|
424
|
+
if [[ "$USER_ID" =~ ^[0-9]+$ ]]; then
|
|
425
|
+
break
|
|
426
|
+
else
|
|
427
|
+
echo
|
|
428
|
+
echo -e " ${RED}✗${NC} That looks like a username, not a numeric ID."
|
|
429
|
+
echo -e " Telegram user IDs are numbers only (e.g. ${BOLD}1588190837${NC})."
|
|
430
|
+
echo -e " Get yours from ${BOLD}@userinfobot${NC} in Telegram."
|
|
431
|
+
echo
|
|
432
|
+
fi
|
|
433
|
+
done
|
|
413
434
|
|
|
414
435
|
if [ -n "$USER_ID" ]; then
|
|
415
436
|
# Write the config
|
|
@@ -446,6 +467,15 @@ if [[ "$SHOW_GUIDE" =~ ^[Yy] ]]; then
|
|
|
446
467
|
echo "$TOKEN_LINE" >> "$SHELL_RC"
|
|
447
468
|
echo -e " ${GREEN}✓${NC} Bot token saved to ~/${SHELL_RC##*/}"
|
|
448
469
|
fi
|
|
470
|
+
|
|
471
|
+
echo
|
|
472
|
+
echo -e " ${YELLOW}┌─────────────────────────────────────────────────┐${NC}"
|
|
473
|
+
echo -e " ${YELLOW}│${NC} ${BOLD}Open a NEW terminal${NC} to run the gateway. ${YELLOW}│${NC}"
|
|
474
|
+
echo -e " ${YELLOW}│${NC} This terminal doesn't have your token yet. ${YELLOW}│${NC}"
|
|
475
|
+
echo -e " ${YELLOW}│${NC} ${YELLOW}│${NC}"
|
|
476
|
+
echo -e " ${YELLOW}│${NC} Or run in this terminal first: ${YELLOW}│${NC}"
|
|
477
|
+
echo -e " ${YELLOW}│${NC} ${CYAN}source ~/${SHELL_RC##*/}${NC} ${YELLOW}│${NC}"
|
|
478
|
+
echo -e " ${YELLOW}└─────────────────────────────────────────────────┘${NC}"
|
|
449
479
|
else
|
|
450
480
|
echo -e " ${YELLOW}!${NC} Add this to your shell profile to persist the token:"
|
|
451
481
|
echo -e " ${DIM}${TOKEN_LINE}${NC}"
|
package/gateway/src/index.js
CHANGED
|
@@ -112,6 +112,28 @@ async function cmdStart(args) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// Pre-flight: check for missing tokens before starting
|
|
116
|
+
const preflight = loadConfig();
|
|
117
|
+
const telegramEnabled = preflight.channels?.telegram?.enabled || overrides.channels?.telegram?.enabled;
|
|
118
|
+
const slackEnabled = preflight.channels?.slack?.enabled || overrides.channels?.slack?.enabled;
|
|
119
|
+
if (telegramEnabled && !process.env.TELEGRAM_BOT_TOKEN && !preflight.channels?.telegram?.token) {
|
|
120
|
+
console.error('Telegram is enabled but TELEGRAM_BOT_TOKEN is not set in this terminal.');
|
|
121
|
+
console.error('');
|
|
122
|
+
console.error('Your token was likely saved to your shell profile during install.');
|
|
123
|
+
console.error('Fix: Open a NEW terminal window and run claudia-gateway start');
|
|
124
|
+
console.error(' Or: source ~/.zshrc (or ~/.bashrc)');
|
|
125
|
+
console.error(' Or: export TELEGRAM_BOT_TOKEN="your-token-here"');
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
if (slackEnabled && !process.env.SLACK_BOT_TOKEN && !preflight.channels?.slack?.token) {
|
|
129
|
+
console.error('Slack is enabled but SLACK_BOT_TOKEN is not set in this terminal.');
|
|
130
|
+
console.error('');
|
|
131
|
+
console.error('Fix: Open a NEW terminal window and run claudia-gateway start');
|
|
132
|
+
console.error(' Or: export SLACK_BOT_TOKEN="your-token-here"');
|
|
133
|
+
console.error(' Or: export SLACK_APP_TOKEN="your-token-here"');
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
115
137
|
const gateway = new Gateway(overrides);
|
|
116
138
|
|
|
117
139
|
// Graceful shutdown
|
|
@@ -137,7 +159,9 @@ async function cmdStart(args) {
|
|
|
137
159
|
console.log(` Channels: ${[...gateway.adapters.keys()].join(', ') || 'none'}`);
|
|
138
160
|
console.log(` Memory: ${gateway.bridge?.memoryAvailable ? 'connected' : 'unavailable'}`);
|
|
139
161
|
console.log(` PID: ${process.pid}`);
|
|
140
|
-
console.log('
|
|
162
|
+
console.log('');
|
|
163
|
+
console.log('This terminal is now dedicated to the gateway. Use Claude in a separate terminal.');
|
|
164
|
+
console.log('Press Ctrl+C to stop.\n');
|
|
141
165
|
|
|
142
166
|
// Keep process alive
|
|
143
167
|
await new Promise(() => {});
|
package/gateway/src/router.js
CHANGED
|
@@ -28,6 +28,7 @@ export class Router {
|
|
|
28
28
|
this.auth = auth;
|
|
29
29
|
this.adapters = adapters;
|
|
30
30
|
this.sessions = new Map(); // sessionKey -> { history: [], lastActive: Date }
|
|
31
|
+
this._rejectedUsers = new Set(); // Track users we've already sent a rejection reply to
|
|
31
32
|
this._cleanupInterval = null;
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -67,6 +68,29 @@ export class Router {
|
|
|
67
68
|
// 1. Auth check
|
|
68
69
|
if (!this.auth.isAuthorized(channel, userId)) {
|
|
69
70
|
log.warn('Unauthorized message rejected', { channel, userId });
|
|
71
|
+
|
|
72
|
+
// Send a one-time rejection reply so the user knows what's wrong
|
|
73
|
+
const rejectKey = `${channel}:${userId}`;
|
|
74
|
+
if (!this._rejectedUsers.has(rejectKey)) {
|
|
75
|
+
this._rejectedUsers.add(rejectKey);
|
|
76
|
+
const rejectMsg =
|
|
77
|
+
`I don't recognize your user ID. Your ${channel} user ID is: ${userId}\n` +
|
|
78
|
+
`Ask the person who set me up to add it to the allowlist in ~/.claudia/gateway.json`;
|
|
79
|
+
try {
|
|
80
|
+
const adapter = this.adapters.get(channel);
|
|
81
|
+
if (adapter && metadata?.ctx && channel === 'telegram') {
|
|
82
|
+
await metadata.ctx.reply(rejectMsg);
|
|
83
|
+
} else if (adapter && metadata?.say && channel === 'slack') {
|
|
84
|
+
await metadata.say({ text: rejectMsg });
|
|
85
|
+
} else if (adapter) {
|
|
86
|
+
const targetId = metadata?.chatId || metadata?.channelId || userId;
|
|
87
|
+
await adapter.sendMessage(targetId, rejectMsg);
|
|
88
|
+
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
log.debug('Failed to send rejection reply', { channel, userId, error: err.message });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
70
94
|
return;
|
|
71
95
|
}
|
|
72
96
|
|
|
@@ -42,14 +42,42 @@ export class AuthManager {
|
|
|
42
42
|
|
|
43
43
|
// Fall back to global allowlist
|
|
44
44
|
if (this.globalAllowed.size > 0) {
|
|
45
|
-
|
|
45
|
+
if (this.globalAllowed.has(userIdStr)) return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Denied -- figure out why and log an actionable message
|
|
49
|
+
const channelEntries = this.channelAllowed[channel]
|
|
50
|
+
? [...this.channelAllowed[channel]]
|
|
51
|
+
: [];
|
|
52
|
+
const globalEntries = [...this.globalAllowed];
|
|
53
|
+
const allEntries = [...channelEntries, ...globalEntries];
|
|
54
|
+
|
|
55
|
+
if (allEntries.length === 0) {
|
|
56
|
+
// No allowlists configured at all
|
|
57
|
+
log.warn('No allowlists configured - denying all access. Add user IDs to gateway config.', {
|
|
58
|
+
channel,
|
|
59
|
+
userId: userIdStr,
|
|
60
|
+
});
|
|
61
|
+
} else if (allEntries.some(e => !/^\d+$/.test(e))) {
|
|
62
|
+
// Allowlist contains non-numeric entries (likely usernames instead of IDs)
|
|
63
|
+
const nonNumeric = allEntries.filter(e => !/^\d+$/.test(e));
|
|
64
|
+
log.warn(
|
|
65
|
+
'Auth denied: allowlist may contain usernames instead of numeric IDs. ' +
|
|
66
|
+
'Telegram requires numeric user IDs (get yours from @userinfobot).', {
|
|
67
|
+
channel,
|
|
68
|
+
userId: userIdStr,
|
|
69
|
+
hint: 'Replace usernames with numeric IDs in gateway.json',
|
|
70
|
+
suspectEntries: nonNumeric,
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
} else {
|
|
74
|
+
// Allowlist exists with numeric IDs, user just isn't in it
|
|
75
|
+
log.warn('Auth denied: user not in allowlist.', {
|
|
76
|
+
channel,
|
|
77
|
+
userId: userIdStr,
|
|
78
|
+
});
|
|
46
79
|
}
|
|
47
80
|
|
|
48
|
-
// If no allowlists configured at all, deny by default (secure default)
|
|
49
|
-
log.warn('No allowlists configured - denying all access. Add user IDs to gateway config.', {
|
|
50
|
-
channel,
|
|
51
|
-
userId: userIdStr,
|
|
52
|
-
});
|
|
53
81
|
return false;
|
|
54
82
|
}
|
|
55
83
|
}
|
|
@@ -59,4 +59,50 @@ describe('AuthManager', () => {
|
|
|
59
59
|
assert.equal(auth.isAuthorized('telegram', 12345), true);
|
|
60
60
|
assert.equal(auth.isAuthorized('telegram', '12345'), true);
|
|
61
61
|
});
|
|
62
|
+
|
|
63
|
+
it('detects non-numeric allowlist entries (username instead of ID)', (t) => {
|
|
64
|
+
const warnings = [];
|
|
65
|
+
// Intercept log.warn by patching console (auth uses createLogger which uses console)
|
|
66
|
+
const auth = new AuthManager({
|
|
67
|
+
globalAllowedUsers: [],
|
|
68
|
+
channels: {
|
|
69
|
+
telegram: { allowedUsers: ['@kamba85'] },
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// The auth check should deny and the warning should reference usernames
|
|
74
|
+
assert.equal(auth.isAuthorized('telegram', '1588190837'), false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('logs generic denial for numeric allowlist mismatch', () => {
|
|
78
|
+
const auth = new AuthManager({
|
|
79
|
+
globalAllowedUsers: [],
|
|
80
|
+
channels: {
|
|
81
|
+
telegram: { allowedUsers: ['999999'] },
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// User not in allowlist, but entries are numeric -- no username warning
|
|
86
|
+
assert.equal(auth.isAuthorized('telegram', '1588190837'), false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('logs no-allowlists warning only when truly empty', () => {
|
|
90
|
+
const auth = new AuthManager({
|
|
91
|
+
globalAllowedUsers: [],
|
|
92
|
+
channels: {
|
|
93
|
+
telegram: { allowedUsers: [] },
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
assert.equal(auth.isAuthorized('telegram', '123'), false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('detects bare username without @ prefix', () => {
|
|
101
|
+
const auth = new AuthManager({
|
|
102
|
+
globalAllowedUsers: ['kamba85'],
|
|
103
|
+
channels: {},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
assert.equal(auth.isAuthorized('telegram', '1588190837'), false);
|
|
107
|
+
});
|
|
62
108
|
});
|