feat(idnot): accepter code en JSON body sur POST /api/v1/idnot/auth | ci: docker_tag=ext

This commit is contained in:
dev4 2025-09-18 19:36:28 +00:00
parent 10832ef375
commit 7f5c7f041c
7 changed files with 44 additions and 26 deletions

View File

@ -89,4 +89,5 @@ STRIPE_STANDARD_ANNUAL_SUBSCRIPTION_PRICE_ID=
STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID= STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID=
STRIPE_UNLIMITED_ANNUAL_SUBSCRIPTION_PRICE_ID= STRIPE_UNLIMITED_ANNUAL_SUBSCRIPTION_PRICE_ID=
SIGNER_API_KEY=your-api-key-change-this SIGNER_API_KEY=your-api-key-change-this
VITE_JWT_SECRET_KEY=52b3d77617bb00982dfee15b08effd52cfe5b2e69b2f61cc4848cfe1e98c0bc9

7
CHANGELOG.md Normal file
View File

@ -0,0 +1,7 @@
## v1.0.1
- IdNot: lendpoint dauthentification accepte désormais le code en POST `/api/v1/idnot/auth` avec `{ code }` dans le corps.
- Handler compatible params/body, recommandation: body JSON.
- Rappel déploiement: image Docker consommée par `lecoffre_node` via tag `ext`.

View File

@ -24,6 +24,10 @@ Analyse synthétique de `lecoffre-back-mini` (Express + TypeScript).
- retry emails (1 min) - retry emails (1 min)
- **Arrêts propres**: gestion `SIGINT`/`SIGTERM`, exceptions et promesses rejetées - **Arrêts propres**: gestion `SIGINT`/`SIGTERM`, exceptions et promesses rejetées
### Changements IdNot
- Lendpoint dauthentification accepte désormais le code en POST corps JSON sur `/api/v1/idnot/auth` (au lieu dun segment dURL).
- Le handler supporte à la fois `req.params.code` et `req.body.code` pour compatibilité, mais lusage recommandé est `req.body.code`.
### Dépendances clés ### Dépendances clés
- **HTTP**: `express`, `cors` - **HTTP**: `express`, `cors`
- **Infra**: `pg`, `dotenv` - **Infra**: `pg`, `dotenv`

View File

@ -1,6 +1,6 @@
{ {
"name": "lecoffre-back-mini", "name": "lecoffre-back-mini",
"version": "1.0.0", "version": "1.0.1",
"description": "Mini serveur avec une route /api/ping", "description": "Mini serveur avec une route /api/ping",
"main": "dist/server.js", "main": "dist/server.js",
"scripts": { "scripts": {

View File

@ -4,21 +4,21 @@ import { asyncHandler } from '../middleware/error-handler';
import { ValidationError, BusinessRuleError } from '../types/errors'; import { ValidationError, BusinessRuleError } from '../types/errors';
/** /**
* Route handlers that extract and validate HTTP request data * Route handlers that extract and validate HTTP request data
* before calling pure controller methods * before calling pure controller methods
*/ */
export class IdNotHandlers { export class IdNotHandlers {
/** /**
* GET /user/rattachements * GET /user/rattachements
* Extract idNot from query params and call controller * Extract idNot from query params and call controller
*/ */
static getUserRattachements = asyncHandler(async (req: Request, res: Response): Promise<void> => { static getUserRattachements = asyncHandler(async (req: Request, res: Response): Promise<void> => {
const requestId = req.headers['x-request-id'] as string; const requestId = req.headers['x-request-id'] as string;
// Extract and validate parameters // Extract and validate parameters
const { idNot } = req.query; const { idNot } = req.query;
if (!idNot || typeof idNot !== 'string') { if (!idNot || typeof idNot !== 'string') {
throw new ValidationError('idNot parameter is required', [ throw new ValidationError('idNot parameter is required', [
{ field: 'idNot', value: idNot, constraints: ['Must be a valid string'] } { field: 'idNot', value: idNot, constraints: ['Must be a valid string'] }
@ -27,20 +27,20 @@ export class IdNotHandlers {
// Call pure controller method with extracted parameters // Call pure controller method with extracted parameters
const result = await IdNotController.getUserRattachements(idNot); const result = await IdNotController.getUserRattachements(idNot);
res.json(result); res.json(result);
}); });
/** /**
* GET /office/rattachements * GET /office/rattachements
* Extract idNot from query params and call controller * Extract idNot from query params and call controller
*/ */
static getOfficeRattachements = asyncHandler(async (req: Request, res: Response): Promise<void> => { static getOfficeRattachements = asyncHandler(async (req: Request, res: Response): Promise<void> => {
const requestId = req.headers['x-request-id'] as string; const requestId = req.headers['x-request-id'] as string;
// Extract and validate parameters // Extract and validate parameters
const { idNot } = req.query; const { idNot } = req.query;
if (!idNot || typeof idNot !== 'string') { if (!idNot || typeof idNot !== 'string') {
throw new ValidationError('idNot parameter is required', [ throw new ValidationError('idNot parameter is required', [
{ field: 'idNot', value: idNot, constraints: ['Must be a valid string'] } { field: 'idNot', value: idNot, constraints: ['Must be a valid string'] }
@ -49,7 +49,7 @@ export class IdNotHandlers {
// Call pure controller method // Call pure controller method
const result = await IdNotController.getOfficeRattachements(idNot); const result = await IdNotController.getOfficeRattachements(idNot);
res.json(result); res.json(result);
}); });
@ -59,10 +59,12 @@ export class IdNotHandlers {
*/ */
static authenticate = asyncHandler(async (req: Request, res: Response): Promise<void> => { static authenticate = asyncHandler(async (req: Request, res: Response): Promise<void> => {
const requestId = req.headers['x-request-id'] as string; const requestId = req.headers['x-request-id'] as string;
// Extract and validate parameters // Extract and validate parameters (support body or URL param)
const { code } = req.params; const codeParam = req.params?.code;
const codeBody = (req.body && typeof req.body.code === 'string') ? req.body.code : undefined;
const code = (codeBody && codeBody.length > 0) ? codeBody : codeParam;
if (!code || typeof code !== 'string' || code.length < 10) { if (!code || typeof code !== 'string' || code.length < 10) {
throw new ValidationError('Invalid authentication code', [ throw new ValidationError('Invalid authentication code', [
{ field: 'code', value: code, constraints: ['Must be a valid authorization code'] } { field: 'code', value: code, constraints: ['Must be a valid authorization code'] }
@ -71,7 +73,7 @@ export class IdNotHandlers {
// Call pure controller method // Call pure controller method
const result = await IdNotController.authenticate(code); const result = await IdNotController.authenticate(code);
res.json(result); res.json(result);
}); });
@ -81,17 +83,17 @@ export class IdNotHandlers {
*/ */
static getCurrentUser = asyncHandler(async (req: Request, res: Response): Promise<void> => { static getCurrentUser = asyncHandler(async (req: Request, res: Response): Promise<void> => {
const requestId = req.headers['x-request-id'] as string; const requestId = req.headers['x-request-id'] as string;
// Extract user info (set by authenticateIdNot middleware) // Extract user info (set by authenticateIdNot middleware)
if (!req.idNotUser) { if (!req.idNotUser) {
throw new BusinessRuleError('User authentication required', undefined, requestId); throw new BusinessRuleError('User authentication required', undefined, requestId);
} }
const { idNot, authToken } = req.idNotUser; const { idNot, authToken } = req.idNotUser;
// Call pure controller method // Call pure controller method
const result = await IdNotController.getCurrentUser(idNot, authToken); const result = await IdNotController.getCurrentUser(idNot, authToken);
res.json(result); res.json(result);
}); });
@ -101,16 +103,16 @@ export class IdNotHandlers {
*/ */
static logout = asyncHandler(async (req: Request, res: Response): Promise<void> => { static logout = asyncHandler(async (req: Request, res: Response): Promise<void> => {
const requestId = req.headers['x-request-id'] as string; const requestId = req.headers['x-request-id'] as string;
if (!req.idNotUser) { if (!req.idNotUser) {
throw new BusinessRuleError('User authentication required', undefined, requestId); throw new BusinessRuleError('User authentication required', undefined, requestId);
} }
const { authToken } = req.idNotUser; const { authToken } = req.idNotUser;
// Call pure controller method // Call pure controller method
const result = await IdNotController.logout(authToken); const result = await IdNotController.logout(authToken);
res.json(result); res.json(result);
}); });
@ -120,16 +122,16 @@ export class IdNotHandlers {
*/ */
static validateToken = asyncHandler(async (req: Request, res: Response): Promise<void> => { static validateToken = asyncHandler(async (req: Request, res: Response): Promise<void> => {
const requestId = req.headers['x-request-id'] as string; const requestId = req.headers['x-request-id'] as string;
if (!req.idNotUser) { if (!req.idNotUser) {
throw new BusinessRuleError('User authentication required', undefined, requestId); throw new BusinessRuleError('User authentication required', undefined, requestId);
} }
const { idNot } = req.idNotUser; const { idNot } = req.idNotUser;
// Call pure controller method // Call pure controller method
const result = await IdNotController.validateToken(idNot); const result = await IdNotController.validateToken(idNot);
res.json(result); res.json(result);
}); });
} }

View File

@ -6,7 +6,7 @@ const router = Router();
router.get('/user/rattachements', IdNotHandlers.getUserRattachements); router.get('/user/rattachements', IdNotHandlers.getUserRattachements);
router.get('/office/rattachements', IdNotHandlers.getOfficeRattachements); router.get('/office/rattachements', IdNotHandlers.getOfficeRattachements);
router.post('/auth/:code', IdNotHandlers.authenticate); router.post('/auth', IdNotHandlers.authenticate);
router.get('/user', authenticateIdNot, IdNotHandlers.getCurrentUser); router.get('/user', authenticateIdNot, IdNotHandlers.getCurrentUser);
router.post('/logout', authenticateIdNot, IdNotHandlers.logout); router.post('/logout', authenticateIdNot, IdNotHandlers.logout);

View File

@ -14,3 +14,7 @@ Axes de tests pour `lecoffre-back-mini` (sans exemples dimplémentation).
### Sécurité ### Sécurité
- **Entrées**: validation, schémas, erreurs typées - **Entrées**: validation, schémas, erreurs typées
- **Secrets**: vérification de la non-exposition via réponses API - **Secrets**: vérification de la non-exposition via réponses API
### Auth IdNot
- Vérifier POST `/api/v1/idnot/auth` avec un code long dans le corps JSON (`{ code }`) retourne un statut applicatif (4xx/5xx) mais pas 502.
- Vérifier quun GET/POST avec segment dURL trop long nest plus utilisé par le front.