From f2702fedfae71e78bc949c69e5bc423464f12059 Mon Sep 17 00:00:00 2001 From: Stivan-Lucas Date: Wed, 1 Apr 2026 11:51:06 -0300 Subject: [PATCH 1/2] chore: update vscode settings for Biome and workspace dictionary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Configura Biome como formatador padrão para TypeScript. Adiciona termos técnicos do projeto (OmniNexus, HWID, Drizzle) ao dicionário do VS Code. --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2db0e90..da6a907 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,6 +22,7 @@ "Fastify", "GITHUB", "healthcheck", + "HWID", "isready", "omni", "omninexus", From 66c5fa0ba20d7869b8b1a25e5a8d4ccd4cd59edb Mon Sep 17 00:00:00 2001 From: Stivan-Lucas Date: Wed, 1 Apr 2026 11:57:08 -0300 Subject: [PATCH 2/2] feat(api): implement agent keys management and auth refactor --- src/languages/en/common.json | 6 +++ src/languages/pt/common.json | 6 +++ src/modules/users/users.service.ts | 2 +- src/routes/auth/login.ts | 2 +- src/routes/index.ts | 2 + src/routes/keys/index.ts | 10 ++++ src/routes/keys/patch-reset.ts | 33 +++++++++++++ src/routes/keys/post-validate.ts | 79 ++++++++++++++++++++++++++++++ src/routes/keys/post.ts | 37 ++++++++++++++ src/types/fastify-jwt.d.ts | 2 + 10 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 src/routes/keys/index.ts create mode 100644 src/routes/keys/patch-reset.ts create mode 100644 src/routes/keys/post-validate.ts create mode 100644 src/routes/keys/post.ts diff --git a/src/languages/en/common.json b/src/languages/en/common.json index cbaec17..1cf4f01 100644 --- a/src/languages/en/common.json +++ b/src/languages/en/common.json @@ -1,5 +1,11 @@ { "welcome": "Hello World", + "keys": { + "created": "Created {{quantity}} key(s) for user {{name}}", + "resetSuccess": "Key reset successfully", + "invalidOrInactive": "Invalid or inactive key", + "alreadyLinkedToOther": "This key is already linked to another user" + }, "auth": { "unauthorized": "Unauthorized access", "invalidCredentials": "Invalid email or password", diff --git a/src/languages/pt/common.json b/src/languages/pt/common.json index 4224641..e6e731b 100644 --- a/src/languages/pt/common.json +++ b/src/languages/pt/common.json @@ -1,5 +1,11 @@ { "welcome": "Olá Mundo", + "keys": { + "created": "Criada {{quantity}} chave(s) para o usuário {{name}}", + "resetSuccess": "Chave redefinida com sucesso", + "invalidOrInactive": "Chave inválida ou inativa", + "alreadyLinkedToOther": "Esta chave já está vinculada a outro usuário" + }, "auth": { "unauthorized": "Acesso não autorizado", "invalidCredentials": "Email ou senha inválidos", diff --git a/src/modules/users/users.service.ts b/src/modules/users/users.service.ts index 3659351..b74f341 100644 --- a/src/modules/users/users.service.ts +++ b/src/modules/users/users.service.ts @@ -1,7 +1,7 @@ import bcrypt from 'bcrypt' import { eq } from 'drizzle-orm' +import { users } from '../../database' import { db } from '../../database/db' -import { users } from '../../database/schema' export const UserService = { async createMaster(data: { name: string; email: string; password: string }) { diff --git a/src/routes/auth/login.ts b/src/routes/auth/login.ts index a85d423..3f3276c 100644 --- a/src/routes/auth/login.ts +++ b/src/routes/auth/login.ts @@ -39,7 +39,7 @@ export async function loginRoute(app: FastifyTypedInstance) { } const token = app.jwt.sign( - { id: user.id, email: user.email, role: user.role }, + { id: user.id, email: user.email, role: user.role, name: user.name }, { expiresIn: '1d', algorithm: 'HS256' }, ) diff --git a/src/routes/index.ts b/src/routes/index.ts index 3b4429a..8cbb240 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,5 +1,6 @@ import type { FastifyTypedInstance } from '../types/fastify' import { authRoutes } from './auth' +import { agentKeysRoutes } from './keys' import { userRoutes } from './users' import { welcomeRoutes } from './welcome' @@ -7,4 +8,5 @@ export async function registerRoutes(app: FastifyTypedInstance) { await app.register(welcomeRoutes) await app.register(authRoutes) await app.register(userRoutes) + await app.register(agentKeysRoutes) } diff --git a/src/routes/keys/index.ts b/src/routes/keys/index.ts new file mode 100644 index 0000000..996249c --- /dev/null +++ b/src/routes/keys/index.ts @@ -0,0 +1,10 @@ +import type { FastifyTypedInstance } from '../../types/fastify' +import { resetKeyRoute } from './patch-reset' +import { createKeyRoute } from './post' +import { validateKeyRoute } from './post-validate' + +export async function agentKeysRoutes(app: FastifyTypedInstance) { + await app.register(createKeyRoute) + await app.register(resetKeyRoute) + await app.register(validateKeyRoute) +} diff --git a/src/routes/keys/patch-reset.ts b/src/routes/keys/patch-reset.ts new file mode 100644 index 0000000..5232064 --- /dev/null +++ b/src/routes/keys/patch-reset.ts @@ -0,0 +1,33 @@ +import { eq } from 'drizzle-orm' +import { z } from 'zod' +import { agentKeys } from '../../database' +import { db } from '../../database/db' +import type { FastifyTypedInstance } from '../../types/fastify' + +const ResetKeyParamsSchema = z.object({ + id: z.uuid(), +}) + +export async function resetKeyRoute(app: FastifyTypedInstance) { + app.patch( + '/keys/:id/reset', + { + onRequest: [app.authenticate], + schema: { + tags: ['Keys'], + summary: 'Reset Hardware ID for a key (allow reuse)', + params: ResetKeyParamsSchema, + }, + }, + async (request) => { + const { id } = request.params + + await db + .update(agentKeys) + .set({ hardwareId: null, activatedAt: null }) + .where(eq(agentKeys.id, id)) + + return { message: request.t('keys.resetSuccess') } + }, + ) +} diff --git a/src/routes/keys/post-validate.ts b/src/routes/keys/post-validate.ts new file mode 100644 index 0000000..4c79561 --- /dev/null +++ b/src/routes/keys/post-validate.ts @@ -0,0 +1,79 @@ +import { and, eq } from 'drizzle-orm' +import { z } from 'zod' +import { agentKeys } from '../../database' +import { db } from '../../database/db' +import type { FastifyTypedInstance } from '../../types/fastify' + +// Schema de validação do corpo da requisição +const ValidateKeyBodySchema = z.object({ + key: z.string().min(1), + hardwareId: z.string().min(1), +}) + +export async function validateKeyRoute(app: FastifyTypedInstance) { + app.post( + '/keys/validate', + { + schema: { + tags: ['Agent'], + summary: 'Validate key and link hardware ID', + body: ValidateKeyBodySchema, + response: { + 200: z.object({ + valid: z.boolean(), + message: z.string(), + }), + 401: z.object({ valid: z.boolean(), message: z.string() }), + 403: z.object({ valid: z.boolean(), message: z.string() }), + }, + }, + }, + async (request, reply) => { + const { key, hardwareId } = request.body + + // Busca a chave ativa no banco + const foundKey = await db.query.agentKeys.findFirst({ + where: and(eq(agentKeys.key, key), eq(agentKeys.isActive, true)), + }) + + // 1. Validação de existência e status + if (!foundKey) { + return reply.status(401).send({ + valid: false, + message: request.t('keys.invalidOrInactive'), + }) + } + + // 2. Proteção contra Hardware diferente (Anti-pirataria/Compartilhamento) + if (foundKey.hardwareId && foundKey.hardwareId !== hardwareId) { + return reply.status(403).send({ + valid: false, + message: request.t('keys.alreadyLinkedToOther'), + }) + } + + // 3. Caso o hardware seja o MESMO (Re-validação/Reboot do PC) + if (foundKey.hardwareId === hardwareId) { + return reply.status(200).send({ + valid: true, + message: 'Authorized (Session restored)', + }) + } + + // 4. Primeira ativação (hardwareId é null no banco) + // Vincula o ID da máquina e registra a data de ativação + await db + .update(agentKeys) + .set({ + hardwareId, + activatedAt: new Date(), + }) + .where(eq(agentKeys.id, foundKey.id)) + + return reply.status(200).send({ + valid: true, + message: 'Authorized and hardware linked', + }) + }, + ) +} diff --git a/src/routes/keys/post.ts b/src/routes/keys/post.ts new file mode 100644 index 0000000..2e589dd --- /dev/null +++ b/src/routes/keys/post.ts @@ -0,0 +1,37 @@ +import { randomBytes } from 'node:crypto' +import { z } from 'zod' +import { agentKeys } from '../../database' +import { db } from '../../database/db' +import type { FastifyTypedInstance } from '../../types/fastify' + +export async function createKeyRoute(app: FastifyTypedInstance) { + app.post( + '/keys', + { + onRequest: [app.authenticate], + schema: { + tags: ['Keys'], + summary: 'Generate new agent keys for a user', + body: z.object({ + quantity: z.number().min(1).max(100), + }), + }, + }, + async (request, reply) => { + const { quantity } = request.body + + const userId = request.user.id + const name = request.user.name + + const newKeys = Array.from({ length: quantity }).map(() => ({ + userId, + key: `OMNI-${randomBytes(4).toString('hex').toUpperCase()}-${randomBytes(4).toString('hex').toUpperCase()}`, + })) + + await db.insert(agentKeys).values(newKeys) + return reply + .status(201) + .send({ message: request.t('keys.created', { quantity, name }) }) + }, + ) +} diff --git a/src/types/fastify-jwt.d.ts b/src/types/fastify-jwt.d.ts index fdf9a86..625ded4 100644 --- a/src/types/fastify-jwt.d.ts +++ b/src/types/fastify-jwt.d.ts @@ -7,11 +7,13 @@ declare module '@fastify/jwt' { id: string email: string role: string + name: string } user: { id: string email: string role: string + name: string } } }