Skip to main content
Glama
auth.service.ts11.4 kB
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { ApiClientService } from '../shared/http/api-client.service'; import { TimesheetConfigService } from '../config/config.service'; @Injectable() export class AuthService { constructor( private readonly apiClient: ApiClientService, private readonly configService: TimesheetConfigService, ) {} /** * Login with username and password */ async login(username: string, password: string): Promise<any> { const response = await this.apiClient.login(username, password); if (response.success && response.data) { // Save token and user data const { token, user } = response.data; this.configService.setToken(token); this.configService.setUser(user); // Auto-fetch and cache available projects, modules, and activities try { await this.fetchAndCacheAvailableData(token); } catch (error) { // Log warning but don't fail login if data fetch fails console.warn('Failed to fetch available data:', error.message); } // Check if user needs to configure their project/modules const isConfigured = this.configService.isUserConfigured(); const needsConfig = this.configService.needsConfiguration(); return { success: true, message: isConfigured ? 'Login successful' : 'Login successful. Please configure your project and modules.', user, needsConfiguration: !isConfigured, configurationNeeds: needsConfig, }; } throw new UnauthorizedException('Login failed: Invalid credentials'); } /** * Fetch and cache available projects, modules, and activities from API */ async fetchAndCacheAvailableData(token: string): Promise<void> { try { // Fetch all data in parallel const [projectsResponse, activitiesResponse] = await Promise.all([ this.apiClient.getProjects(token), this.apiClient.getActivities(token), ]); // Extract and store projects if (projectsResponse.success && projectsResponse.data) { const projects = Array.isArray(projectsResponse.data) ? projectsResponse.data : [projectsResponse.data]; const availableProjects = projects.map((p: any) => ({ id: p.project_id || p.id, name: p.project_name || p.name, })); this.configService.setAvailableProjects(availableProjects); } // Extract and store activities if (activitiesResponse.success && activitiesResponse.data) { const activities = Array.isArray(activitiesResponse.data) ? activitiesResponse.data : [activitiesResponse.data]; const availableActivities = activities.map((a: any) => ({ id: a.activity_id || a.id, name: a.activity_name || a.name, })); this.configService.setAvailableActivities(availableActivities); } // Fetch modules for all projects const allModules: Array<{ id: number; name: string; project_id: number }> = []; if (projectsResponse.success && projectsResponse.data) { const projects = Array.isArray(projectsResponse.data) ? projectsResponse.data : [projectsResponse.data]; for (const project of projects) { const projectId = project.project_id || project.id; try { const modulesResponse = await this.apiClient.getModules(projectId, token); if (modulesResponse.success && modulesResponse.data) { const modules = Array.isArray(modulesResponse.data) ? modulesResponse.data : [modulesResponse.data]; modules.forEach((m: any) => { allModules.push({ id: m.module_id || m.id, name: m.module_name || m.name, project_id: m.project_id || projectId, }); }); } } catch (error) { console.warn(`Failed to fetch modules for project ${projectId}:`, error.message); } } } // Store all modules if (allModules.length > 0) { this.configService.setAvailableModules(allModules); } } catch (error) { throw new Error(`Failed to fetch available data: ${error.message}`); } } /** * Manually refresh available data from API */ async refreshAvailableData(): Promise<void> { const token = await this.ensureAuthenticated(); await this.fetchAndCacheAvailableData(token); } /** * Refresh modules for a specific project */ async refreshModulesForProject(projectId: number): Promise<void> { const token = await this.ensureAuthenticated(); try { const modulesResponse = await this.apiClient.getModules(projectId, token); if (modulesResponse.success && modulesResponse.data) { const modules = Array.isArray(modulesResponse.data) ? modulesResponse.data : [modulesResponse.data]; // Get existing modules and filter out old ones for this project const existingModules = this.configService.getAvailableModules(); const filteredModules = existingModules.filter((m) => m.project_id !== projectId); // Add new modules for this project const newModules = modules.map((m: any) => ({ id: m.module_id || m.id, name: m.module_name || m.name, project_id: m.project_id || projectId, })); // Update config with combined modules this.configService.setAvailableModules([...filteredModules, ...newModules]); } } catch (error) { throw new Error(`Failed to fetch modules for project ${projectId}: ${error.message}`); } } /** * Automatically configure user with their assigned project/modules * Called during auto-login - fetches user-specific project and auto-assigns */ async autoConfigureUserAssignments(userId: number): Promise<{ success: boolean; configured: boolean; message: string; project?: any; modules?: any[]; }> { try { const token = this.getToken(); // Fetch user-specific project const userProjectsResponse = await this.apiClient.getUserProjects(userId, token); if (!userProjectsResponse.success || !userProjectsResponse.data) { return { success: false, configured: false, message: 'Failed to fetch user project', }; } const userProjects = Array.isArray(userProjectsResponse.data) ? userProjectsResponse.data : [userProjectsResponse.data]; // Get the first (and only) project const project = userProjects[0]; if (!project || !(project.project_id || project.id)) { return { success: false, configured: false, message: 'No project assigned to user', }; } const projectId = project.project_id || project.id; const projectName = project.project_name || project.name; // Fetch modules for this project const modulesResponse = await this.apiClient.getModules(projectId, token); if (!modulesResponse.success || !modulesResponse.data) { return { success: false, configured: false, message: `Found project "${projectName}" but failed to fetch modules`, }; } const modules = Array.isArray(modulesResponse.data) ? modulesResponse.data : [modulesResponse.data]; if (modules.length === 0) { return { success: false, configured: false, message: `Project "${projectName}" has no modules assigned`, }; } // Auto-assign all modules const moduleIds = modules.map((m: any) => m.module_id || m.id); const result = await this.configureUserAssignments(projectId, moduleIds); return { success: true, configured: true, message: `Auto-configured: "${projectName}" with ${modules.length} module(s)`, project: result.project, modules: result.modules, }; } catch (error) { return { success: false, configured: false, message: `Auto-configuration error: ${error.message}`, }; } } /** * Configure user's assigned project and modules */ async configureUserAssignments( projectId: number, moduleIds: number[], ): Promise<{ success: boolean; message: string; project?: any; modules?: any[]; }> { // Validate project exists in available projects const availableProjects = this.configService.getAvailableProjects(); const selectedProject = availableProjects.find((p) => p.id === projectId); if (!selectedProject) { throw new Error(`Project with ID ${projectId} not found in available projects`); } // Validate modules exist and belong to selected project const availableModules = this.configService.getAvailableModules(); const selectedModules = availableModules.filter( (m) => moduleIds.includes(m.id) && m.project_id === projectId, ); if (selectedModules.length !== moduleIds.length) { throw new Error('Some module IDs are invalid or do not belong to selected project'); } // Save configuration this.configService.setUserAssignedProject(selectedProject); this.configService.setUserAssignedModules(selectedModules); return { success: true, message: `Configuration saved successfully`, project: selectedProject, modules: selectedModules, }; } /** * Logout - clear stored credentials */ logout(): void { this.configService.clearConfig(); } /** * Get current JWT token */ getToken(): string { const token = this.configService.getToken(); if (!token) { throw new UnauthorizedException('Not authenticated. Please login first.'); } return token; } /** * Get current user */ getCurrentUser(): any { const user = this.configService.getUser(); if (!user) { throw new UnauthorizedException('Not authenticated. Please login first.'); } return user; } /** * Refresh JWT token */ async refreshToken(): Promise<string> { const currentToken = this.getToken(); try { const response = await this.apiClient.refreshToken(currentToken); if (response.success && response.data) { const { token, user } = response.data; this.configService.setToken(token); this.configService.setUser(user); return token; } throw new Error('Token refresh failed'); } catch (error) { // If refresh fails, clear config and require re-login this.logout(); throw new UnauthorizedException('Session expired. Please login again.'); } } /** * Check if user is authenticated */ isAuthenticated(): boolean { return this.configService.isConfigured(); } /** * Ensure user is authenticated, refresh token if needed */ async ensureAuthenticated(): Promise<string> { if (!this.isAuthenticated()) { throw new UnauthorizedException('Not configured. Please login first.'); } try { return this.getToken(); } catch (error) { // Try to refresh token return await this.refreshToken(); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/arshad-khan1/Timesheet-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server