feat(F-048): completed feature

This commit is contained in:
chattie
2026-08-19 07:17:14 +02:00
parent 8ee1938af9
commit 835ab66eda
187 changed files with 12361 additions and 1065 deletions

View File

@@ -3,6 +3,8 @@
* calls use cases, maps domain errors to the shared error envelope.
*/
import type { FastifyInstance, FastifyReply } from 'fastify';
import type { FastifySchema } from 'fastify';
import { errorSchema } from '../../../shared/swagger.js';
import { z } from 'zod';
import type { Authenticate } from '../../../shared/auth.js';
import { parseJson } from '../../../shared/http-input.js';
@@ -66,7 +68,98 @@ export async function registerIdentityRoutes(
});
const logout = new Logout(sessions, hashSessionToken);
app.post('/auth/register', async (request, reply) => {
const registerSchema: FastifySchema = {
tags: ['Auth'],
summary: 'Register new user',
description: 'Crea una cuenta de usuario. Por defecto el rol es `customer`.',
body: {
type: 'object',
required: ['email', 'password'],
properties: {
email: { type: 'string', format: 'email', maxLength: 255, description: 'Email único' },
password: {
type: 'string',
minLength: 8,
maxLength: 128,
description: 'Contraseña (mín. 8 caracteres)',
},
},
},
response: {
201: {
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' },
email: { type: 'string', format: 'email' },
role: { type: 'string', enum: ['customer', 'editor', 'admin'] },
createdAt: { type: 'string', format: 'date-time' },
},
},
409: errorSchema,
},
};
const loginSchema: FastifySchema = {
tags: ['Auth'],
summary: 'Login',
description: 'Inicia sesión y devuelve una cookie de sesión `mdv_session` (válida 7 días).',
body: {
type: 'object',
required: ['email', 'password'],
properties: {
email: { type: 'string', format: 'email', description: 'Email' },
password: { type: 'string', description: 'Contraseña' },
},
},
response: {
200: {
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' },
email: { type: 'string', format: 'email' },
role: { type: 'string', enum: ['customer', 'editor', 'admin'] },
},
},
401: errorSchema,
429: errorSchema,
},
};
const logoutSchema: FastifySchema = {
tags: ['Auth'],
summary: 'Logout',
description: 'Invalida la sesión actual y elimina la cookie.',
response: { 204: { type: 'null' } },
};
const meSchema: FastifySchema = {
tags: ['Auth'],
summary: 'Current user',
description:
'Devuelve el usuario autenticado (cookie de sesión) o `{user: null}` si no hay sesión.',
response: {
200: {
oneOf: [
{
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' },
email: { type: 'string', format: 'email' },
role: { type: 'string', enum: ['customer', 'editor', 'admin'] },
},
},
{
type: 'object',
properties: {
user: { type: 'null' },
},
},
],
},
},
};
app.post('/auth/register', { schema: registerSchema }, async (request, reply) => {
const input = parseJson(credentialsSchema, request.body);
try {
const user = await registerUser.execute(input);
@@ -81,7 +174,7 @@ export async function registerIdentityRoutes(
}
});
app.post('/auth/login', async (request, reply) => {
app.post('/auth/login', { schema: loginSchema }, async (request, reply) => {
const input = parseJson(credentialsSchema, request.body);
try {
const result = await login.execute(input);
@@ -101,14 +194,14 @@ export async function registerIdentityRoutes(
}
});
app.post('/auth/logout', async (request, reply) => {
app.post('/auth/logout', { schema: logoutSchema }, async (request, reply) => {
const token = request.cookies[SESSION_COOKIE_NAME];
await logout.execute(token);
clearSessionCookie(reply, cookieSecure);
return reply.code(204).send();
});
app.get('/auth/me', async (request, reply) => {
app.get('/auth/me', { schema: meSchema }, async (request, reply) => {
try {
const user = await deps.authenticate!(request);
return reply.send({ id: user.id, email: user.email, role: user.role });