npm package: @shield-acl/fastify
Description: Plugin Fastify para integração com Shield ACL - Autorização declarativa para APIs REST e arquiteturas DDD
Author: Anderson D. Rosa
Latest version: 1.1.0

# @shield-acl/fastify

<div align="center">
  <img src="https://raw.githubusercontent.com/andersondrosa/andersondrosa/refs/heads/main/images/shield-acl.png" alt="Shield ACL Fastify" width="400" />
</div>

<div align="center">
  <p><strong>Plugin Fastify para integração com Shield ACL</strong></p>
  <p>Autorização declarativa para APIs REST e arquiteturas DDD</p>
</div>

## Características

- 🚀 **Plugin Fastify Nativo**: Integração perfeita com Fastify 4.x e 5.x
- 🛡️ **Middleware de Autorização**: Proteja rotas com verificações declarativas
- 🎯 **Suporte a DDD**: Guards para Use Cases com autorização integrada
- 🔌 **Decorators Úteis**: `request.can()`, `fastify.authorize()` e mais
- 📝 **TypeScript First**: Totalmente tipado com IntelliSense completo
- ⚡ **Performance**: Otimizado para alta performance com cache

## Instalação

```bash
npm install @shield-acl/fastify @shield-acl/core
# ou
yarn add @shield-acl/fastify @shield-acl/core
# ou
pnpm add @shield-acl/fastify @shield-acl/core
```

## Uso Rápido

### 1. Registrar o Plugin

```typescript
import Fastify from 'fastify'
import { ACL } from '@shield-acl/core'
import shieldACLPlugin from '@shield-acl/fastify'

const fastify = Fastify()
const acl = new ACL()

// Definir roles
acl.defineRole({
  name: 'admin',
  permissions: [{ action: '*', resource: '*' }]
})

acl.defineRole({
  name: 'user',
  permissions: [
    { action: 'read', resource: 'posts' },
    { action: 'create', resource: 'posts' }
  ]
})

// Registrar plugin
await fastify.register(shieldACLPlugin, {
  acl,
  getUserFromRequest: async (request) => {
    // Extrair usuário do JWT, sessão, etc
    const token = request.headers.authorization?.replace('Bearer ', '')
    if (!token) return null
    
    // Decodificar token e retornar usuário
    const decoded = await verifyToken(token)
    return {
      id: decoded.sub,
      roles: decoded.roles,
      attributes: decoded.attributes
    }
  }
})
```

### 2. Proteger Rotas

```typescript
// Usando preHandler
fastify.get('/admin', {
  preHandler: fastify.authorize({
    action: 'admin',
    resource: '*'
  })
}, async (request, reply) => {
  return { message: 'Welcome admin!' }
})

// Múltiplas verificações
fastify.post('/posts', {
  preHandler: [
    fastify.authenticate(), // seu middleware de autenticação
    fastify.authorize({
      action: 'create',
      resource: 'posts'
    })
  ]
}, async (request, reply) => {
  return { post: 'created' }
})

// Com recurso dinâmico
fastify.put('/posts/:id', {
  preHandler: fastify.authorize({
    action: 'update',
    resource: (request) => `posts:${request.params.id}`,
    context: async (request) => ({
      post: await getPost(request.params.id)
    })
  })
}, async (request, reply) => {
  return { updated: true }
})
```

### 3. Verificações Programáticas

```typescript
fastify.get('/my-permissions', async (request, reply) => {
  // Verificar permissões via request
  const canCreatePosts = request.can('create', 'posts')
  const canDeletePosts = request.can('delete', 'posts')
  
  // Avaliação detalhada
  const evaluation = request.evaluate('publish', 'posts')
  
  return {
    permissions: {
      createPosts: canCreatePosts,
      deletePosts: canDeletePosts
    },
    publishEvaluation: evaluation
  }
})

// Verificar via instância Fastify
fastify.get('/check-user/:userId', async (request, reply) => {
  const user = await getUser(request.params.userId)
  const canEdit = fastify.can(user, 'edit', 'profile')
  
  return { canEditProfile: canEdit }
})
```

## Uso com DDD (Domain-Driven Design)

### 1. Proteger Use Cases

```typescript
// Use Case
class CreatePostUseCase implements ProtectedUseCase<CreatePostInput, Post> {
  constructor(private postRepository: PostRepository) {}
  
  async execute(input: CreatePostInput, user: User): Promise<Post> {
    const post = new Post({
      ...input,
      authorId: user.id
    })
    
    return this.postRepository.save(post)
  }
}

// Criar guard
const guardedCreatePost = fastify.createUseCaseGuard(
  new CreatePostUseCase(postRepository),
  {
    getPermissions: (input) => ({
      action: 'create',
      resource: 'posts'
    }),
    context: (input, user) => ({
      category: input.category
    })
  }
)

// Usar em rota
fastify.post('/posts', async (request, reply) => {
  const post = await guardedCreatePost(request.body, request.user)
  return { post }
})
```

### 2. Decorator para Use Cases

```typescript
import { RequirePermissions } from '@shield-acl/fastify'

class PostService {
  @RequirePermissions({
    getPermissions: (input) => ({
      action: 'update',
      resource: `posts:${input.id}`
    })
  })
  async updatePost(input: UpdatePostInput, user: User): Promise<Post> {
    // Lógica do use case
  }
  
  @RequirePermissions({
    getPermissions: (input) => [
      { action: 'delete', resource: `posts:${input.id}` },
      { action: 'moderate', resource: 'posts' }
    ]
  })
  async deletePost(input: { id: string }, user: User): Promise<void> {
    // Precisa de ambas as permissões
  }
}
```

## API Completa

### Opções do Plugin

```typescript
interface ShieldACLOptions {
  // Instância do ACL (obrigatório)
  acl: ACL
  
  // Função para extrair usuário da requisição
  getUserFromRequest?: (request: FastifyRequest) => User | null | Promise<User | null>
  
  // Nome da propriedade onde o usuário será armazenado (default: "user")
  userProperty?: string
  
  // Handler customizado para erros de autorização
  errorHandler?: (error: UnauthorizedError, request: FastifyRequest, reply: FastifyReply) => void
  
  // Debug mode (default: false)
  debug?: boolean
}
```

### Decorators do Fastify

```typescript
// Instância do ACL
fastify.acl: ACL

// Criar middleware de autorização
fastify.authorize(options: AuthorizeOptions): preHandlerHookHandler

// Verificar permissão
fastify.can(user: User, action: string, resource?: string, context?: any): boolean

// Criar guard para use case
fastify.createUseCaseGuard<TInput, TOutput>(
  useCase: ProtectedUseCase<TInput, TOutput>,
  options: UseCaseGuardOptions<TInput>
): (input: TInput, user?: User) => Promise<TOutput>
```

### Decorators da Request

```typescript
// Usuário atual
request.user: User | undefined

// Verificar permissão
request.can(action: string, resource?: string, context?: any): boolean

// Avaliação detalhada
request.evaluate(action: string, resource?: string, context?: any): EvaluationResult
```

### Opções de Autorização

```typescript
interface AuthorizeOptions {
  // Ação requerida
  action: string
  
  // Recurso (pode ser função)
  resource?: string | ((request: FastifyRequest) => string)
  
  // Contexto adicional (pode ser função async)
  context?: any | ((request: FastifyRequest) => any | Promise<any>)
  
  // Condição para pular verificação
  skip?: (request: FastifyRequest) => boolean | Promise<boolean>
  
  // Error handler específico
  errorHandler?: (error: UnauthorizedError, request: FastifyRequest, reply: FastifyReply) => void
}
```

## Exemplos Avançados

### Autorização Condicional

```typescript
fastify.get('/posts/:id', {
  preHandler: fastify.authorize({
    action: 'read',
    resource: 'posts',
    skip: async (request) => {
      // Pular autorização se o post é público
      const post = await getPost(request.params.id)
      return post.visibility === 'public'
    }
  })
}, handler)
```

### Contexto Dinâmico

```typescript
fastify.delete('/posts/:id', {
  preHandler: fastify.authorize({
    action: 'delete',
    resource: 'posts',
    context: async (request) => {
      const post = await getPost(request.params.id)
      return {
        resource: post,
        isOwner: post.authorId === request.user?.id
      }
    }
  })
}, handler)
```

### Error Handler Customizado

```typescript
await fastify.register(shieldACLPlugin, {
  acl,
  errorHandler: (error, request, reply) => {
    // Log detalhado
    request.log.warn({
      user: request.user?.id,
      action: error.action,
      resource: error.resource,
      reason: error.reason
    }, 'Authorization denied')
    
    // Resposta customizada
    reply.status(403).send({
      error: 'Forbidden',
      message: 'Você não tem permissão para esta ação',
      details: process.env.NODE_ENV === 'development' ? {
        action: error.action,
        resource: error.resource
      } : undefined
    })
  }
})
```

### Integração com Swagger

```typescript
fastify.get('/admin/users', {
  schema: {
    security: [{ bearerAuth: [] }],
    tags: ['admin'],
    summary: 'List all users',
    response: {
      200: {
        type: 'array',
        items: { $ref: 'User#' }
      },
      403: {
        type: 'object',
        properties: {
          error: { type: 'string' },
          message: { type: 'string' }
        }
      }
    }
  },
  preHandler: fastify.authorize({
    action: 'read',
    resource: 'users'
  })
}, async (request, reply) => {
  return await userService.findAll()
})
```

## Performance

- Cache automático de avaliações via Shield ACL Core
- Decorators são criados apenas uma vez durante o registro
- Verificações síncronas quando possível
- Suporte a condições assíncronas apenas quando necessário

## Testes

```bash
# Rodar testes
pnpm test

# Coverage
pnpm test:coverage

# Watch mode
pnpm test:watch
```

## Compatibilidade

- Fastify 4.x
- Fastify 5.x
- Node.js 18+
- TypeScript 4.5+

## Licença

MIT © Anderson D. Rosa