Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"Fastify",
"GITHUB",
"healthcheck",
"HWID",
"isready",
"omni",
"omninexus",
Expand Down
6 changes: 6 additions & 0 deletions src/languages/en/common.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
6 changes: 6 additions & 0 deletions src/languages/pt/common.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/modules/users/users.service.ts
Original file line number Diff line number Diff line change
@@ -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 }) {
Expand Down
2 changes: 1 addition & 1 deletion src/routes/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
)

Expand Down
2 changes: 2 additions & 0 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { FastifyTypedInstance } from '../types/fastify'
import { authRoutes } from './auth'
import { agentKeysRoutes } from './keys'
import { userRoutes } from './users'
import { welcomeRoutes } from './welcome'

export async function registerRoutes(app: FastifyTypedInstance) {
await app.register(welcomeRoutes)
await app.register(authRoutes)
await app.register(userRoutes)
await app.register(agentKeysRoutes)
}
10 changes: 10 additions & 0 deletions src/routes/keys/index.ts
Original file line number Diff line number Diff line change
@@ -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)
}
33 changes: 33 additions & 0 deletions src/routes/keys/patch-reset.ts
Original file line number Diff line number Diff line change
@@ -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') }
},
)
}
79 changes: 79 additions & 0 deletions src/routes/keys/post-validate.ts
Original file line number Diff line number Diff line change
@@ -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',
})
},
)
}
37 changes: 37 additions & 0 deletions src/routes/keys/post.ts
Original file line number Diff line number Diff line change
@@ -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 }) })
},
)
}
2 changes: 2 additions & 0 deletions src/types/fastify-jwt.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Expand Down
Loading