import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { AuthService } from './auth.service';
/**
* Auto-Login Service
*
* Automatically authenticates users on server startup using credentials
* from environment variables (TIMESHEET_USERNAME and TIMESHEET_PASSWORD).
*
* This is the ONLY authentication method available. Manual login has been
* removed to prevent credential exposure to LLM providers.
*
* Security Benefits:
* - Credentials never exposed to LLM or MCP client
* - No credentials in chat logs or request traces
* - Configured once in .env or MCP client config
* - Auto-login on server startup
*
* Required Configuration:
* - TIMESHEET_USERNAME: User email address
* - TIMESHEET_PASSWORD: User password
*/
@Injectable()
export class AutoLoginService implements OnModuleInit {
private readonly logger = new Logger(AutoLoginService.name);
constructor(private readonly authService: AuthService) {}
/**
* Lifecycle hook that runs after module initialization
* Attempts auto-login if credentials are present in environment
*/
async onModuleInit() {
await this.attemptAutoLogin();
}
/**
* Attempts to auto-login using environment variables
*
* Reads TIMESHEET_USERNAME and TIMESHEET_PASSWORD from environment.
* If both are present, attempts authentication with backend API.
*
* Fails silently if:
* - Environment variables not set
* - Authentication fails
* - User already authenticated
*
* This allows fallback to manual login tool if needed.
*/
private async attemptAutoLogin(): Promise<void> {
try {
// Check if user is already authenticated
try {
const existingToken = this.authService.getToken();
if (existingToken) {
this.logger.log('User already authenticated (existing token found)');
const currentUser = this.authService.getCurrentUser();
if (currentUser) {
this.logger.log(
`Authenticated as: ${currentUser.name} (${currentUser.username})`,
);
}
return;
}
} catch (error) {
// No existing token, proceed with auto-login attempt
this.logger.log('No existing authentication found, attempting auto-login...');
}
// Read credentials from environment variables
const username = process.env.TIMESHEET_USERNAME;
const password = process.env.TIMESHEET_PASSWORD;
// Debug logging to help diagnose configuration issues
this.logger.debug(`Environment check - TIMESHEET_USERNAME present: ${!!username}`);
this.logger.debug(`Environment check - TIMESHEET_PASSWORD present: ${!!password}`);
// Credentials are required - no fallback available
if (!username || !password) {
this.logger.warn(
'⚠️ Authentication required: TIMESHEET_USERNAME or TIMESHEET_PASSWORD not set',
);
this.logger.warn(
'⚠️ Please configure credentials in your MCP server configuration (env section)',
);
this.logger.warn(
'⚠️ Example MCP config:',
);
this.logger.warn(
' "timesheet-mcp": {',
);
this.logger.warn(
' "command": "node",',
);
this.logger.warn(
' "args": ["path/to/dist/main.js"],',
);
this.logger.warn(
' "env": {',
);
this.logger.warn(
' "TIMESHEET_USERNAME": "your.email@company.com",',
);
this.logger.warn(
' "TIMESHEET_PASSWORD": "your-password"',
);
this.logger.warn(
' }',
);
this.logger.warn(
' }',
);
this.logger.warn(
'⚠️ Server will start but most tools will require authentication',
);
return;
}
// Validate credentials format
if (!this.isValidEmail(username)) {
this.logger.warn(
`Auto-login skipped: TIMESHEET_USERNAME is not a valid email format`,
);
return;
}
if (password.length < 1) {
this.logger.warn(
'Auto-login skipped: TIMESHEET_PASSWORD is empty',
);
return;
}
// Attempt login
this.logger.log(`Attempting auto-login for user: ${username}`);
const result = await this.authService.login(username, password);
if (result.success && result.user) {
this.logger.log(
`✓ Auto-login successful: ${result.user.name} (${result.user.username})`,
);
this.logger.log(
`User roles: ${result.user.roles?.join(', ') || 'No roles'}`,
);
// Attempt automatic project/module configuration
if (result.user?.id) {
try {
const autoConfigResult =
await this.authService.autoConfigureUserAssignments(
result.user.id,
);
if (autoConfigResult.configured) {
this.logger.log(`✓ ${autoConfigResult.message}`);
if (autoConfigResult.project) {
this.logger.log(
` Project: ${autoConfigResult.project.name} (ID: ${autoConfigResult.project.id})`,
);
}
if (autoConfigResult.modules && autoConfigResult.modules.length > 0) {
const moduleNames = autoConfigResult.modules
.map((m) => m.name)
.join(', ');
this.logger.log(
` Modules (${autoConfigResult.modules.length}): ${moduleNames}`,
);
}
} else if (autoConfigResult.success) {
// Not configured but no error (e.g., multiple projects)
this.logger.warn(`⚠️ ${autoConfigResult.message}`);
} else {
// Configuration failed
this.logger.warn(
`⚠️ Auto-configuration failed: ${autoConfigResult.message}`,
);
this.logger.warn(
` Use quick_configure tool to configure manually`,
);
}
} catch (error) {
this.logger.warn(
`⚠️ Auto-configuration error: ${error.message}`,
);
this.logger.warn(` Use quick_configure tool to configure manually`);
}
} else {
// Fallback to old configuration check
if (result.needsConfiguration) {
this.logger.warn(
'⚠️ Configuration needed: Use quick_configure tool',
);
} else {
this.logger.log('✓ User configuration complete');
}
}
} else {
this.logger.error(
`Auto-login failed: Invalid credentials or authentication error`,
);
this.logger.error(
'Please check your TIMESHEET_USERNAME and TIMESHEET_PASSWORD',
);
}
} catch (error) {
// Log error but don't throw - allow server to start even if auto-login fails
this.logger.error(
`Auto-login error: ${error.message}`,
);
this.logger.error(
'Please verify your credentials and API connection',
);
}
}
/**
* Validates email format
* Simple regex check for basic email validation
*/
private isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
}