lecoffre-back-mini/src/controllers/idnot.controller.ts
2025-09-07 23:24:26 +02:00

248 lines
7.9 KiB
TypeScript

import fetch from 'node-fetch';
import { v4 as uuidv4 } from 'uuid';
import { IdNotService } from '../services/idnot';
import { authTokens } from '../utils/auth-tokens';
import { IdNotUser, AuthToken } from '../types';
import { Logger } from '../utils/logger';
import { NotFoundError, ExternalServiceError, BusinessRuleError } from '../types/errors';
/**
* Pure controller methods that handle business logic
* without depending on Express Request/Response objects
*/
export class IdNotController {
/**
* Get user rattachements by idNot
*/
static async getUserRattachements(idNot: string): Promise<any[]> {
Logger.info('Getting user rattachements', { idNot });
const json = await IdNotService.getUserRattachements(idNot);
// Check if any rattachements found
if (!json.result || json.result.length === 0) {
throw new NotFoundError('No rattachements found');
}
// Get office data for each rattachement
const officeData = await Promise.all(json.result.map(async (result: any) => {
const searchParams = new URLSearchParams({
key: process.env.IDNOT_API_KEY || '',
deleted: 'false'
});
try {
const response = await fetch(`${process.env.IDNOT_ANNUARY_BASE_URL}${result.entiteUrl}?` + searchParams, {
method: 'GET'
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
Logger.error('Failed to fetch office data', {
entiteUrl: result.entiteUrl,
error: error instanceof Error ? error.message : 'Unknown error'
});
throw new ExternalServiceError('IdNot', `Failed to fetch office data: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}));
Logger.info('Successfully retrieved user rattachements', {
idNot,
count: officeData.length
});
return officeData;
}
/**
* Get office rattachements by office idNot
*/
static async getOfficeRattachements(idNot: string): Promise<any> {
Logger.info('Getting office rattachements', { idNot });
try {
const result = await IdNotService.getOfficeRattachements(idNot);
Logger.info('Successfully retrieved office rattachements', {
idNot,
count: result.result?.length || 0
});
return result;
} catch (error) {
Logger.error('Failed to get office rattachements', {
idNot,
error: error instanceof Error ? error.message : 'Unknown error'
});
throw new ExternalServiceError('IdNot', `Failed to get office rattachements: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Authenticate user with authorization code
*/
static async authenticate(code: string): Promise<{ idNotUser: IdNotUser; authToken: string }> {
Logger.info('IdNot authentication initiated', { codePrefix: code.substring(0, 8) + '...' });
try {
// Exchange code for tokens
const tokens = await IdNotService.exchangeCodeForTokens(code);
const jwt = tokens.id_token;
if (!jwt) {
throw new BusinessRuleError('No ID token received from IdNot');
}
// Decode JWT payload
const payload = JSON.parse(Buffer.from(jwt.split('.')[1], 'base64').toString('utf8'));
// Get user data
const userData = await IdNotService.getUserData(payload.profile_idn);
if (!userData || !userData.statutDuRattachement || userData.entite.typeEntite.name !== 'office') {
throw new BusinessRuleError('User not attached to an office');
}
// Get office location data
const officeLocationData = await IdNotService.getOfficeLocationData(userData.entite.locationsUrl);
if (!officeLocationData || !officeLocationData.result || officeLocationData.result.length === 0) {
throw new BusinessRuleError('Office location data not found');
}
// Build IdNotUser object
const idNotUser: IdNotUser = {
idNot: payload.sub,
office: {
idNot: payload.entity_idn,
name: userData.entite.denominationSociale ?? userData.entite.codeCrpcen,
crpcen: userData.entite.codeCrpcen,
office_status: IdNotService.getOfficeStatus(userData.entite.statutEntite.name),
address: {
address: officeLocationData.result[0].adrGeo4,
city: officeLocationData.result[0].adrGeoVille.split(' ')[0] ?? officeLocationData.result[0].adrGeoVille,
zip_code: Number(officeLocationData.result[0].adrGeoCodePostal)
},
status: 'ACTIVE'
},
role: IdNotService.getRole(userData.typeLien.name),
contact: {
first_name: userData.personne.prenom,
last_name: userData.personne.nomUsuel,
email: userData.mailRattachement,
phone_number: userData.numeroTelephone,
cell_phone_number: userData.numeroMobile ?? userData.numeroTelephone,
civility: IdNotService.getCivility(userData.personne.civilite)
},
office_role: IdNotService.getOfficeRole(userData.typeLien.name)
};
if (!idNotUser.contact.email) {
throw new BusinessRuleError('User professional email is empty');
}
// Generate auth token
const authToken = uuidv4();
const tokenData: AuthToken = {
idNot: idNotUser.idNot,
authToken,
idNotUser: idNotUser,
pairingId: null,
defaultStorage: null,
createdAt: Date.now(),
expiresAt: Date.now() + (24 * 60 * 60 * 1000) // 24 hours
};
authTokens.push(tokenData);
Logger.info('IdNot authentication successful', {
idNot: idNotUser.idNot,
office: idNotUser.office.name
});
return { idNotUser, authToken };
} catch (error) {
Logger.error('IdNot authentication failed', {
codePrefix: code.substring(0, 8) + '...',
error: error instanceof Error ? error.message : 'Unknown error'
});
if (error instanceof BusinessRuleError || error instanceof ExternalServiceError) {
throw error;
}
throw new ExternalServiceError('IdNot', `Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Get current user data by idNot and authToken
*/
static async getCurrentUser(idNot: string, authToken: string): Promise<{ success: boolean; data: IdNotUser }> {
Logger.info('Getting current user data', { idNot });
// Find the full token data
const userAuth = authTokens.find(auth => auth.authToken === authToken);
if (!userAuth || !userAuth.idNotUser) {
throw new NotFoundError('User data not found. Please log in again.');
}
Logger.info('Current user data retrieved', {
idNot,
office: userAuth.idNotUser.office.name
});
return {
success: true,
data: userAuth.idNotUser
};
}
/**
* Logout user by removing auth token
*/
static async logout(authToken: string): Promise<{ success: boolean; message: string }> {
Logger.info('User logout initiated');
// Remove the auth token from the array
const tokenIndex = authTokens.findIndex(auth => auth.authToken === authToken);
if (tokenIndex > -1) {
const removedToken = authTokens.splice(tokenIndex, 1)[0];
Logger.info('User logout successful', {
idNot: removedToken.idNot
});
} else {
Logger.warn('Logout attempted with invalid token');
}
return {
success: true,
message: 'Déconnexion réussie'
};
}
/**
* Validate if a token is still valid
*/
static async validateToken(idNot: string): Promise<{ success: boolean; message: string; data: { idNot: string; valid: boolean } }> {
Logger.debug('Token validation requested', { idNot });
return {
success: true,
message: 'Token valide',
data: {
idNot,
valid: true
}
};
}
}