import { createError, readBody, type H3Event } from 'h3' import { signAccessToken } from './jwt' import { getAuthRuntimeConfig } from './auth-config' import { verifyPassword } from './password' import { prisma } from './prisma' import { issueRefreshToken, rotateRefreshToken } from './refresh-token' interface LoginBody { email?: unknown password?: unknown } interface RefreshBody { refresh_token?: unknown } /** * Valida e normaliza email/senha enviados no login. * * @param body Corpo bruto da requisição de login. * @returns Email normalizado e senha pronta para validação. * @throws {H3Error} Quando email ou senha não forem informados. */ function parseCredentialBody(body: LoginBody) { const email = typeof body.email === 'string' ? body.email.trim().toLowerCase() : '' const password = typeof body.password === 'string' ? body.password : '' if (!email || !password) { throw createError({ statusCode: 400, statusMessage: 'email and password are required' }) } return { email, password } } /** * Valida o refresh token enviado no corpo da requisição. * * @param body Corpo bruto da requisição de refresh. * @returns Refresh token em formato de string. * @throws {H3Error} Quando `refresh_token` não for informado. */ function parseRefreshBody(body: RefreshBody): string { const refreshToken = typeof body.refresh_token === 'string' ? body.refresh_token.trim() : '' if (!refreshToken) { throw createError({ statusCode: 400, statusMessage: 'refresh_token is required' }) } return refreshToken } /** * Executa o fluxo de login: * valida credenciais, gera access token e emite refresh token. * * @param event Evento HTTP da requisição. * @returns Resposta padrão de autenticação para o cliente. * @throws {H3Error} Quando as credenciais forem inválidas ou faltarem dados. */ export async function handleLogin(event: H3Event) { const config = getAuthRuntimeConfig(event) const body = await readBody(event) const { email, password } = parseCredentialBody(body ?? {}) const user = await prisma.user.findUnique({ where: { email } }) if (!user || !verifyPassword(password, user.passwordHash)) { throw createError({ statusCode: 401, statusMessage: 'Invalid credentials' }) } const accessToken = await signAccessToken(event, { sub: user.id }) const refreshToken = await issueRefreshToken(event, user.id) return { access_token: accessToken, refresh_token: refreshToken.token, token_type: 'Bearer', expires_in: config.accessTtlSec } } /** * Executa o fluxo de refresh: * valida o refresh token, rotaciona e retorna novo access token. * * @param event Evento HTTP da requisição. * @returns Novo par de tokens para continuar a sessão. * @throws {H3Error} Quando o refresh token for inválido ou ausente. */ export async function handleRefresh(event: H3Event) { const config = getAuthRuntimeConfig(event) const body = await readBody(event) const incomingRefreshToken = parseRefreshBody(body ?? {}) const rotated = await rotateRefreshToken(event, incomingRefreshToken) const accessToken = await signAccessToken(event, { sub: rotated.user.id }) return { access_token: accessToken, refresh_token: rotated.token, token_type: 'Bearer', expires_in: config.accessTtlSec } }