server: Allow empty username to restore b2 behaviour. Fixes #59

This commit is contained in:
Peter Steinberger 2025-06-23 16:55:26 +02:00
parent 38c308e34c
commit 9101613351
3 changed files with 37 additions and 21 deletions

View file

@ -142,7 +142,8 @@ final class BunServer {
.replacingOccurrences(of: "$", with: "\\$") .replacingOccurrences(of: "$", with: "\\$")
.replacingOccurrences(of: "`", with: "\\`") .replacingOccurrences(of: "`", with: "\\`")
.replacingOccurrences(of: "\\", with: "\\\\") .replacingOccurrences(of: "\\", with: "\\\\")
vibetunnelArgs += " --username admin --password \"\(escapedPassword)\"" // Use password-only mode for better UX - users can enter any username
vibetunnelArgs += " --password \"\(escapedPassword)\""
} }
} }

View file

@ -52,12 +52,25 @@ export function createAuthMiddleware(config: AuthConfig) {
const credentials = Buffer.from(base64Credentials, 'base64').toString('utf8'); const credentials = Buffer.from(base64Credentials, 'base64').toString('utf8');
const [username, password] = credentials.split(':'); const [username, password] = credentials.split(':');
// If no username is configured, accept any username as long as password matches
// This allows for password-only authentication mode
if (!config.basicAuthUsername) {
// Password-only mode: ignore username, only check password
if (password === config.basicAuthPassword) {
logger.log(chalk.green(`authenticated via password-only mode from ${req.ip}`));
return next();
} else {
logger.warn(`failed password-only auth attempt from ${req.ip}`);
}
} else {
// Username+password mode: check both
if (username === config.basicAuthUsername && password === config.basicAuthPassword) { if (username === config.basicAuthUsername && password === config.basicAuthPassword) {
return next(); return next();
} else { } else {
logger.warn(`failed basic auth attempt from ${req.ip} for user: ${username}`); logger.warn(`failed basic auth attempt from ${req.ip} for user: ${username}`);
} }
} }
}
// No valid auth provided // No valid auth provided
logger.warn(`unauthorized request to ${req.method} ${req.path} from ${req.ip}`); logger.warn(`unauthorized request to ${req.method} ${req.path} from ${req.ip}`);

View file

@ -218,14 +218,9 @@ function parseArgs(): Config {
// Validate configuration // Validate configuration
function validateConfig(config: ReturnType<typeof parseArgs>) { function validateConfig(config: ReturnType<typeof parseArgs>) {
// Validate local auth configuration // Validate local auth configuration
if ( if (config.basicAuthUsername && !config.basicAuthPassword) {
(config.basicAuthUsername && !config.basicAuthPassword) || logger.error('Password must be provided when username is specified');
(!config.basicAuthUsername && config.basicAuthPassword) logger.error('Use --username and --password together');
) {
logger.error('Both username and password must be provided for authentication');
logger.error(
'Use --username and --password, or set both VIBETUNNEL_USERNAME and VIBETUNNEL_PASSWORD'
);
process.exit(1); process.exit(1);
} }
@ -267,10 +262,11 @@ function validateConfig(config: ReturnType<typeof parseArgs>) {
} }
// If not HQ mode and no HQ URL, warn about authentication // If not HQ mode and no HQ URL, warn about authentication
if (!config.basicAuthUsername && !config.basicAuthPassword && !config.isHQMode && !config.hqUrl) { if (!config.basicAuthPassword && !config.isHQMode && !config.hqUrl) {
logger.warn('No authentication configured'); logger.warn('No authentication configured');
logger.warn('Set VIBETUNNEL_PASSWORD or use --password flag for password-only authentication');
logger.warn( logger.warn(
'Set VIBETUNNEL_USERNAME and VIBETUNNEL_PASSWORD or use --username and --password flags' 'Or use --username and --password flags together for username+password authentication'
); );
} }
} }
@ -509,7 +505,7 @@ export async function createApp(): Promise<AppInstance> {
createPushRoutes({ createPushRoutes({
vapidManager, vapidManager,
pushNotificationService, pushNotificationService,
bellEventHandler, bellEventHandler: bellEventHandler ?? undefined,
}) })
); );
logger.debug('Mounted push notification routes'); logger.debug('Mounted push notification routes');
@ -576,14 +572,20 @@ export async function createApp(): Promise<AppInstance> {
chalk.green(`VibeTunnel Server running on http://${displayAddress}:${actualPort}`) chalk.green(`VibeTunnel Server running on http://${displayAddress}:${actualPort}`)
); );
if (config.basicAuthUsername && config.basicAuthPassword) { if (config.basicAuthPassword) {
logger.log(chalk.green('Basic authentication: ENABLED')); if (config.basicAuthUsername) {
logger.log(chalk.green('Authentication: USERNAME + PASSWORD'));
logger.log(`Username: ${config.basicAuthUsername}`); logger.log(`Username: ${config.basicAuthUsername}`);
logger.log(`Password: ${'*'.repeat(config.basicAuthPassword.length)}`); logger.log(`Password: ${'*'.repeat(config.basicAuthPassword.length)}`);
} else {
logger.log(chalk.green('Authentication: PASSWORD ONLY'));
logger.log(`Password: ${'*'.repeat(config.basicAuthPassword.length)}`);
logger.log(chalk.gray('(Any username will be accepted)'));
}
} else { } else {
logger.warn('Server running without authentication'); logger.warn('Server running without authentication');
logger.warn( logger.warn(
'Anyone can access this server. Use --username and --password or set VIBETUNNEL_USERNAME and VIBETUNNEL_PASSWORD' 'Anyone can access this server. Use --password for password-only auth or --username and --password for full auth'
); );
} }
@ -705,7 +707,7 @@ export async function startVibeTunnelServer() {
if (appInstance.pushNotificationService) { if (appInstance.pushNotificationService) {
_subscriptionCleanupInterval = setInterval( _subscriptionCleanupInterval = setInterval(
() => { () => {
appInstance.pushNotificationService!.cleanupInactiveSubscriptions().catch((error) => { appInstance.pushNotificationService?.cleanupInactiveSubscriptions().catch((error) => {
logger.error('Failed to cleanup inactive subscriptions:', error); logger.error('Failed to cleanup inactive subscriptions:', error);
}); });
}, },