Projeto full-stack (Angular + Node/Express/TypeScript) para autenticação via OTP por e-mail e gestão de tickets integrados ao Jira.
- Visão Geral e Arquitetura
- Requisitos
- Configuração (Backend e Frontend)
- Variáveis de Ambiente
- Como Rodar em Desenvolvimento
- Build de Produção (Frontend) e Execução do Backend
- Fluxo de Autenticação (OTP, JWT e Refresh Token)
- Integração com Jira (Endpoints e Regras)
- Regras de Negócio Implementadas
- Upload de Anexos (Limites e Tipos)
- Boas Práticas e Segurança
- Solução de Problemas (FAQ)
- Backend: Node.js + Express + TypeScript
- Rotas de autenticação (OTP) e rotas de integração Jira sob
/api(protegidas por JWT). - Logs de erro em
backend/logs/error.log.
- Rotas de autenticação (OTP) e rotas de integração Jira sob
- Frontend: Angular 19 standalone + Angular Material
- Interceptor HTTP envia
Authorization: Bearer <token>. - Proxy em dev para backend:
proxy.conf.json.
- Interceptor HTTP envia
Fluxo:
- Usuário solicita OTP; recebe por e-mail.
- Usuário valida OTP; backend emite Access Token (curto) + Refresh Token (cookie httpOnly).
- Frontend usa o Access Token para chamar
/api/*. Se 401, faz refresh e repete.
- Node.js 18+
- NPM 9+
- Angular CLI (opcional)
- Conta Jira Cloud com token de API
- Servidor SMTP (para envio de OTP)
- Instale dependências:
cd backend
npm install
- Crie o arquivo
backend/.env(ver Variáveis de Ambiente abaixo). - Rode em dev:
npm run dev
- Instale dependências:
cd frontend
npm install
- Rode com proxy:
npm start -- --proxy-config proxy.conf.json
Acesse: http://localhost:4200
Obrigatórias e recomendadas:
# Servidor
PORT=3000
# Jira
JIRA_URL=https://SEU-DOMINIO.atlassian.net
JIRA_EMAIL=seu-email@dominio.com
JIRA_API_TOKEN=seu-token-api
JIRA_PROJECT_KEY=DDWB
# E-mail (OTP)
EMAIL_HOST=smtp.seuprovedor.com
EMAIL_PORT=587
EMAIL_SECURE=false
EMAIL_USER=usuario
EMAIL_PASS=senha
EMAIL_FROM="Nome <no-reply@dominio.com>"
ALLOWED_EMAIL_DOMAINS=teste.com.br
# JWT
JWT_SECRET=coloque-um-segredo-forte-aqui
JWT_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d
# (Opcional) domínio do cookie de refresh
# COOKIE_DOMAIN=localhost
# Upload (opcional)
UPLOAD_MAX_BYTES=10485760
UPLOAD_ALLOWED_TYPES=image/png,image/jpeg,application/pdf,text/plain
Notas:
- Gere
JWT_SECRETcom 32–64 bytes aleatórios (ex.:openssl rand -base64 64). - PowerShell
- [Convert]::ToBase64String((New-Object Byte[] 64 | % { (New-Object System.Security.Cryptography.RNGCryptoServiceProvider).GetBytes($) ; $ }))
- Node
- node -e "console.log(require('crypto').randomBytes(64).toString('base64'))"
- Em produção, use
EMAIL_SECURE=truese porta 465/SSL.
Backend e frontend rodam em portas diferentes, com proxy no Angular redirecionando /api e /auth para o backend.
- Backend:
cd backend && npm run dev - Frontend:
cd frontend && npm start -- --proxy-config proxy.conf.json
cd frontend
npm run build
O output fica em frontend/dist/.
- Provisione as variáveis de ambiente
- Inicie o servidor (ex.: PM2, Docker, ou Node) apontando para
backend/src/index.tsvia ts-node ou transpilado se preferir.
Obs.: O projeto não inclui hoje um servidor de arquivos estáticos para servir o Angular em produção; normalmente se usa um web server (Nginx/Apache) ou um host estático (S3/CloudFront) e o backend fica atrás de um reverse proxy no mesmo domínio.
POST /auth/request-otprecebe{ email }, verifica domínio permitido e envia OTP por e-mail (validade 5 min).POST /auth/verify-otprecebe{ email, otp }e, se válido:- Emite
access token(JWT) de curta duração (padrão 15m) retornado no corpo:token. - Emite
refresh token(JWT) de longa duração (padrão 7d) em cookie httpOnlyrefresh_token.
- Emite
- Frontend salva o access token no
localStorage. - Interceptor envia
Authorization: Bearer <token>para/api/*. - Se algum
/api/*retornar 401, o interceptor chamaPOST /auth/refreshpara obter novo access token e repete a requisição.
Middleware no backend protege app.use('/api', authenticateToken, ticketsRouter) validando o Bearer token.
Endpoints no backend (todos sob /api, protegidos por JWT):
POST /api/ticketscria issue no Jira.GET /api/ticketsbusca issues do projeto (exclui status category "Done" e ordena por prioridade desc/created asc). Campos selecionados:summary, assignee, priority, status, timetracking, description, customfield_10020.GET /api/tickets/search?jql=...executa JQL customizada.PUT /api/tickets/:idatualiza issue.DELETE /api/tickets/:idremove issue.GET /api/usersusuários atribuíveis no projeto.POST /api/tickets/:id/attachmentsupload de anexo para a issue.GET /api/tickets/customfield/:fieldId/optionsopções de um custom field (API v3 com contexts/options; cache em memória 5 min).GET /api/sprints/:boardIdobtém sprints (Agile API v1.0; cache em memória 5 min).
Cache em memória (5 min) para sprints e opções de customfield.
Frontend (resumo principal):
- Lista de tickets
- Filtra tickets com status category "Done" (feito na query do backend) e ordena por prioridade desc/creation asc.
- Calcula horas totais gastas no sprint do mês corrente a partir de
timetracking.timeSpentSeconds. - Barra de progresso contra limite de 30h (capped em 100%).
- Polling após criação de ticket: a cada 2s até 20s para garantir que o novo ticket apareça.
- Formulário de ticket
- Prefixa novo
summarycomDDWB_Desenv_. - Busca Epic do mês atual (padrão de nome:
DDWB - <Mês> <Ano>). - Busca Sprint do mês atual (padrão de nome:
Sprint <Mês>) no board id107(fixo no código atual do frontend). - Cria issue com:
project.key = 'DDWB'issuetype = 'desenvolvimento avulso'(deve existir no seu Jira)customfield_10014(Epic Link) = epicKeycustomfield_10020(Sprint) = sprintId
- Limites por prioridade (Highest 2, High 5, Medium 10). Valida antes de criar, consultando contagem no backend.
- Prefixa novo
Observações importantes:
- Os nomes/tipos de issue e IDs de campos (
customfield_10014,customfield_10020) dependem da configuração do seu Jira. Ajuste conforme necessário. - O board id
107está fixo no frontend (arquivoticket-form.component.ts). Caso mude, atualize o valor.
Backend:
backend/src/index.ts:- Middleware
authenticateToken: valida JWT (headerAuthorization: Bearer) comJWT_SECRET. Registra erros emlogs/error.log. - Rotas públicas em
/auth; rotas protegidas em/api.
- Middleware
backend/src/routes/auth.ts:POST /auth/request-otp: envia OTP (SMTP), restringe domínios viaALLOWED_EMAIL_DOMAINS.POST /auth/verify-otp: emite Access (curto) + Refresh (cookie httpOnly), controla expiração comJWT_EXPIRES_IN/JWT_REFRESH_EXPIRES_IN.POST /auth/refresh: renova access token a partir do refresh.- Rate limiting configurado para mitigar abuso.
backend/src/routes/tickets.ts:- Endpoints de CRUD e consulta ao Jira; upload com
multerlimitado por.env.
- Endpoints de CRUD e consulta ao Jira; upload com
backend/src/services/jira.ts:- Cliente Axios para Jira; cache em memória para sprints e opções de custom field (5 min).
backend/src/services/auth.ts:- OTP em memória (apenas dev); configure Redis em produção.
Frontend:
frontend/src/app/services/auth.service.ts:- Guarda/recupera access token no
sessionStorage; refresh via/auth/refresh.
- Guarda/recupera access token no
frontend/src/app/services/auth.interceptor.ts:- Anexa
Authorization: Bearer <token>nas chamadas/api; em 401, tenta refresh e repete.
- Anexa
frontend/src/app/components/ticket-form/ticket-form.component.ts:- Regras de criação (prefixo
DDWB_Desenv_, Epic/Sprint do mês, tipos/fields Jira).
- Regras de criação (prefixo
frontend/src/app/components/ticket-list/ticket-list.component.ts:- Cálculo de horas do sprint atual e progresso até limite (30h), ordenação e polling pós-criação.
- Endpoint:
POST /api/tickets/:id/attachments multerconfigurado com:- Limite de tamanho via
UPLOAD_MAX_BYTES(padrão 10MB) - Tipos permitidos via
UPLOAD_ALLOWED_TYPES(padrãoimage/png,image/jpeg,application/pdf,text/plain)
- Limite de tamanho via
- Envia header
X-Atlassian-Token: no-checkconforme exigência do Jira para anexos.
- Use JWT curto e refresh separado (já implementado). Não armazene refresh token no
localStorage(usamos cookie httpOnly). - Rate limiting em
/authpara reduzir abuso de OTP. - Não comite
.enve segredos. - Ajuste CORS apenas se não usar proxy em dev; em produção, prefira mesmo domínio com reverse proxy.
- Valide e monitore tamanhos/tipos de upload.
- "Server Misconfiguration: JWT_SECRET not set."
- Defina
JWT_SECRETnobackend/.enve reinicie o backend.
- Defina
- 401 em
/api/*após login- Verifique se o frontend está enviando
Authorization: Bearer <token>. - Abra DevTools → Network e confira a requisição para
/api/tickets. - Se 401, veja se
/auth/refreshfoi chamado. Confirme presença do cookierefresh_tokenna resposta de/auth/verify-otp.
- Verifique se o frontend está enviando
- Erro ao criar issue: issuetype/customfield não encontrado
- Ajuste
issuetypee IDscustomfield_10014(Epic Link) ecustomfield_10020conforme sua instância Jira.
- Ajuste
- CORS em dev
- Rode com proxy:
ng serve --proxy-config proxy.conf.json.
- Rode com proxy:
backend/
src/
index.ts
routes/
auth.ts
tickets.ts
services/
auth.ts
jira.ts
utils/
logger.ts
logs/error.log
frontend/
src/app/
components/
services/
guards/
proxy.conf.json
Uso interno. Ajuste conforme a política da sua organização.