devsquad 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/bin/devsquad.js +42 -0
- package/package.json +42 -0
- package/src/init.js +119 -0
- package/templates/.claude/agents/fullcycle-software-architect.md +291 -0
- package/templates/.claude/skills/clickup/SKILL.md +75 -0
- package/templates/.claude/skills/database/SKILL.md +124 -0
- package/templates/.claude/skills/docs/SKILL.md +74 -0
- package/templates/.claude/skills/docs/templates.md +117 -0
- package/templates/.claude/skills/figma/SKILL.md +72 -0
- package/templates/.claude/skills/git/SKILL.md +92 -0
- package/templates/.claude/skills/nestjs/SKILL.md +35 -0
- package/templates/.claude/skills/nestjs/auth.md +187 -0
- package/templates/.claude/skills/nestjs/modules.md +110 -0
- package/templates/.claude/skills/nestjs/security.md +42 -0
- package/templates/.claude/skills/nestjs/setup.md +162 -0
- package/templates/.claude/skills/postman/SKILL.md +82 -0
- package/templates/.claude/skills/react/SKILL.md +53 -0
- package/templates/.claude/skills/react/setup.md +153 -0
- package/templates/.claude/skills/react-native/SKILL.md +82 -0
- package/templates/.claude/skills/react-native/setup.md +150 -0
- package/templates/.claude/skills/security/SKILL.md +80 -0
- package/templates/.claude/skills/security/owasp-full.md +66 -0
- package/templates/CLAUDE.md +65 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# React — Setup completo (Vite + TS)
|
|
2
|
+
|
|
3
|
+
Complementa o **SKILL.md** com arquivos base: API, auth e rotas.
|
|
4
|
+
|
|
5
|
+
## src/services/api.ts
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import axios from 'axios';
|
|
9
|
+
|
|
10
|
+
const api = axios.create({
|
|
11
|
+
baseURL: import.meta.env.VITE_API_URL ?? 'http://localhost:3000/api/v1',
|
|
12
|
+
withCredentials: true,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
api.interceptors.request.use((config) => {
|
|
16
|
+
const token = localStorage.getItem('access_token');
|
|
17
|
+
if (token) config.headers.Authorization = `Bearer ${token}`;
|
|
18
|
+
return config;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
api.interceptors.response.use(
|
|
22
|
+
(r) => r,
|
|
23
|
+
async (error) => {
|
|
24
|
+
// Opcional: refresh token + fila de retries — alinhar com backend
|
|
25
|
+
if (error.response?.status === 401) {
|
|
26
|
+
localStorage.removeItem('access_token');
|
|
27
|
+
window.location.href = '/login';
|
|
28
|
+
}
|
|
29
|
+
return Promise.reject(error);
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export default api;
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## src/contexts/AuthContext.tsx
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import {
|
|
40
|
+
createContext,
|
|
41
|
+
useContext,
|
|
42
|
+
useState,
|
|
43
|
+
useCallback,
|
|
44
|
+
type ReactNode,
|
|
45
|
+
} from 'react';
|
|
46
|
+
import api from '../services/api';
|
|
47
|
+
|
|
48
|
+
type User = { id: string; email: string; name: string };
|
|
49
|
+
|
|
50
|
+
type AuthContextValue = {
|
|
51
|
+
user: User | null;
|
|
52
|
+
loading: boolean;
|
|
53
|
+
login: (email: string, password: string) => Promise<void>;
|
|
54
|
+
logout: () => void;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const AuthContext = createContext<AuthContextValue | null>(null);
|
|
58
|
+
|
|
59
|
+
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
60
|
+
const [user, setUser] = useState<User | null>(null);
|
|
61
|
+
const [loading, setLoading] = useState(false);
|
|
62
|
+
|
|
63
|
+
const login = useCallback(async (email: string, password: string) => {
|
|
64
|
+
setLoading(true);
|
|
65
|
+
try {
|
|
66
|
+
const { data } = await api.post('/auth/login', { email, password });
|
|
67
|
+
localStorage.setItem('access_token', data.access_token);
|
|
68
|
+
setUser(data.user);
|
|
69
|
+
} finally {
|
|
70
|
+
setLoading(false);
|
|
71
|
+
}
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
const logout = useCallback(() => {
|
|
75
|
+
localStorage.removeItem('access_token');
|
|
76
|
+
setUser(null);
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<AuthContext.Provider value={{ user, loading, login, logout }}>
|
|
81
|
+
{children}
|
|
82
|
+
</AuthContext.Provider>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function useAuth() {
|
|
87
|
+
const ctx = useContext(AuthContext);
|
|
88
|
+
if (!ctx) throw new Error('useAuth outside AuthProvider');
|
|
89
|
+
return ctx;
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## src/routes/PrivateRoute.tsx
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { Navigate, Outlet } from 'react-router-dom';
|
|
97
|
+
import { useAuth } from '../contexts/AuthContext';
|
|
98
|
+
|
|
99
|
+
export function PrivateRoute() {
|
|
100
|
+
const { user, loading } = useAuth();
|
|
101
|
+
if (loading) return <div>Carregando…</div>;
|
|
102
|
+
if (!user) return <Navigate to="/login" replace />;
|
|
103
|
+
return <Outlet />;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## src/routes/AppRoutes.tsx
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
|
111
|
+
import { AuthProvider } from '../contexts/AuthContext';
|
|
112
|
+
import { PrivateRoute } from './PrivateRoute';
|
|
113
|
+
// import LoginPage from '../pages/login/LoginPage';
|
|
114
|
+
// import DashboardPage from '../pages/dashboard/DashboardPage';
|
|
115
|
+
|
|
116
|
+
export function AppRoutes() {
|
|
117
|
+
return (
|
|
118
|
+
<BrowserRouter>
|
|
119
|
+
<AuthProvider>
|
|
120
|
+
<Routes>
|
|
121
|
+
<Route path="/login" element={<div>Login</div>} />
|
|
122
|
+
<Route element={<PrivateRoute />}>
|
|
123
|
+
<Route path="/" element={<div>Dashboard</div>} />
|
|
124
|
+
</Route>
|
|
125
|
+
<Route path="*" element={<Navigate to="/" replace />} />
|
|
126
|
+
</Routes>
|
|
127
|
+
</AuthProvider>
|
|
128
|
+
</BrowserRouter>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## src/main.tsx
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { StrictMode } from 'react';
|
|
137
|
+
import { createRoot } from 'react-dom/client';
|
|
138
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
139
|
+
import { AppRoutes } from './routes/AppRoutes';
|
|
140
|
+
import './index.css';
|
|
141
|
+
|
|
142
|
+
const queryClient = new QueryClient();
|
|
143
|
+
|
|
144
|
+
createRoot(document.getElementById('root')!).render(
|
|
145
|
+
<StrictMode>
|
|
146
|
+
<QueryClientProvider client={queryClient}>
|
|
147
|
+
<AppRoutes />
|
|
148
|
+
</QueryClientProvider>
|
|
149
|
+
</StrictMode>,
|
|
150
|
+
);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Substitua os placeholders de página pelos componentes reais em `pages/`.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-native-mobile
|
|
3
|
+
description: Setup e arquitetura mobile com React Native + Expo + NativeWind. Use quando o desenvolvedor precisar inicializar ou trabalhar no app mobile, configurar Expo Router, armazenamento seguro de tokens, navegação ou build com EAS.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# React Native + Expo + NativeWind
|
|
7
|
+
|
|
8
|
+
## Setup rápido
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx create-expo-app@latest nome-do-app --template blank-typescript
|
|
12
|
+
cd nome-do-app
|
|
13
|
+
npx expo install expo-router expo-linking expo-constants expo-status-bar react-native-screens react-native-safe-area-context expo-secure-store
|
|
14
|
+
npm install nativewind tailwindcss && npx tailwindcss init
|
|
15
|
+
npm install axios react-hook-form zod @hookform/resolvers
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Configuração NativeWind
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
// tailwind.config.js
|
|
22
|
+
module.exports = {
|
|
23
|
+
content: ['./app/**/*.{js,jsx,ts,tsx}', './components/**/*.{js,jsx,ts,tsx}'],
|
|
24
|
+
presets: [require('nativewind/preset')],
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
// babel.config.js
|
|
30
|
+
module.exports = function(api) {
|
|
31
|
+
api.cache(true);
|
|
32
|
+
return {
|
|
33
|
+
presets: [['babel-preset-expo', { jsxImportSource: 'nativewind' }], 'nativewind/babel'],
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Estrutura (Expo Router)
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
app/
|
|
42
|
+
├── _layout.tsx ← Root layout com AuthProvider
|
|
43
|
+
├── (auth)/
|
|
44
|
+
│ ├── _layout.tsx ← Redireciona para (app) se autenticado
|
|
45
|
+
│ └── login.tsx
|
|
46
|
+
└── (app)/
|
|
47
|
+
├── _layout.tsx ← Tab bar ou Stack layout
|
|
48
|
+
└── index.tsx ← Dashboard
|
|
49
|
+
components/ui/ ← Button, Input, Card
|
|
50
|
+
services/
|
|
51
|
+
├── api.ts ← Axios + interceptors
|
|
52
|
+
└── storage.service.ts ← SecureStore (NUNCA AsyncStorage para tokens)
|
|
53
|
+
contexts/AuthContext.tsx
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Regra crítica de segurança
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// ✅ CORRETO — SecureStore usa Keychain (iOS) e Keystore (Android)
|
|
60
|
+
import * as SecureStore from 'expo-secure-store';
|
|
61
|
+
await SecureStore.setItemAsync('access_token', token);
|
|
62
|
+
|
|
63
|
+
// ❌ ERRADO — AsyncStorage é texto plano acessível com root
|
|
64
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
65
|
+
await AsyncStorage.setItem('access_token', token); // NUNCA para tokens
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
> 🔒 OWASP M9 — Insecure Data Storage: Use `expo-secure-store` para qualquer dado sensível.
|
|
69
|
+
|
|
70
|
+
## .env.local.example
|
|
71
|
+
|
|
72
|
+
```env
|
|
73
|
+
# IP da máquina na rede local — localhost não funciona no dispositivo físico
|
|
74
|
+
# Windows: ipconfig | Mac/Linux: ifconfig
|
|
75
|
+
EXPO_PUBLIC_API_URL=http://192.168.X.X:3000/api/v1
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Arquivos detalhados desta skill
|
|
79
|
+
|
|
80
|
+
- **setup.md** — AuthContext, api.ts, storage.service.ts, layouts Expo Router completos
|
|
81
|
+
|
|
82
|
+
Leia `setup.md` para implementar a estrutura base completa.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# React Native (Expo) — Setup completo
|
|
2
|
+
|
|
3
|
+
Complementa o **SKILL.md** com API, armazenamento seguro e integração com Expo Router.
|
|
4
|
+
|
|
5
|
+
## services/storage.service.ts
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import * as SecureStore from 'expo-secure-store';
|
|
9
|
+
|
|
10
|
+
const KEYS = { access: 'access_token', refresh: 'refresh_token' } as const;
|
|
11
|
+
|
|
12
|
+
export async function setTokens(access: string, refresh?: string) {
|
|
13
|
+
await SecureStore.setItemAsync(KEYS.access, access);
|
|
14
|
+
if (refresh) await SecureStore.setItemAsync(KEYS.refresh, refresh);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function getAccessToken() {
|
|
18
|
+
return SecureStore.getItemAsync(KEYS.access);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function clearTokens() {
|
|
22
|
+
await SecureStore.deleteItemAsync(KEYS.access);
|
|
23
|
+
await SecureStore.deleteItemAsync(KEYS.refresh);
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## services/api.ts
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import axios from 'axios';
|
|
31
|
+
import { getAccessToken, clearTokens } from './storage.service';
|
|
32
|
+
|
|
33
|
+
const api = axios.create({
|
|
34
|
+
baseURL: process.env.EXPO_PUBLIC_API_URL,
|
|
35
|
+
timeout: 15000,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
api.interceptors.request.use(async (config) => {
|
|
39
|
+
const token = await getAccessToken();
|
|
40
|
+
if (token) config.headers.Authorization = `Bearer ${token}`;
|
|
41
|
+
return config;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
api.interceptors.response.use(
|
|
45
|
+
(r) => r,
|
|
46
|
+
async (error) => {
|
|
47
|
+
if (error.response?.status === 401) {
|
|
48
|
+
await clearTokens();
|
|
49
|
+
// Redirecionar via router — ex.: router.replace('/(auth)/login')
|
|
50
|
+
}
|
|
51
|
+
return Promise.reject(error);
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
export default api;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## contexts/AuthContext.tsx
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import {
|
|
62
|
+
createContext,
|
|
63
|
+
useContext,
|
|
64
|
+
useState,
|
|
65
|
+
useCallback,
|
|
66
|
+
type ReactNode,
|
|
67
|
+
} from 'react';
|
|
68
|
+
import api from '../services/api';
|
|
69
|
+
import * as storage from '../services/storage.service';
|
|
70
|
+
|
|
71
|
+
type User = { id: string; email: string; name: string };
|
|
72
|
+
|
|
73
|
+
type AuthContextValue = {
|
|
74
|
+
user: User | null;
|
|
75
|
+
loading: boolean;
|
|
76
|
+
signIn: (email: string, password: string) => Promise<void>;
|
|
77
|
+
signOut: () => Promise<void>;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const AuthContext = createContext<AuthContextValue | null>(null);
|
|
81
|
+
|
|
82
|
+
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
83
|
+
const [user, setUser] = useState<User | null>(null);
|
|
84
|
+
const [loading, setLoading] = useState(false);
|
|
85
|
+
|
|
86
|
+
const signIn = useCallback(async (email: string, password: string) => {
|
|
87
|
+
setLoading(true);
|
|
88
|
+
try {
|
|
89
|
+
const { data } = await api.post('/auth/login', { email, password });
|
|
90
|
+
await storage.setTokens(data.access_token, data.refresh_token);
|
|
91
|
+
setUser(data.user);
|
|
92
|
+
} finally {
|
|
93
|
+
setLoading(false);
|
|
94
|
+
}
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
const signOut = useCallback(async () => {
|
|
98
|
+
await storage.clearTokens();
|
|
99
|
+
setUser(null);
|
|
100
|
+
}, []);
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<AuthContext.Provider value={{ user, loading, signIn, signOut }}>
|
|
104
|
+
{children}
|
|
105
|
+
</AuthContext.Provider>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function useAuth() {
|
|
110
|
+
const ctx = useContext(AuthContext);
|
|
111
|
+
if (!ctx) throw new Error('useAuth outside AuthProvider');
|
|
112
|
+
return ctx;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## app/_layout.tsx (raiz)
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { Stack } from 'expo-router';
|
|
120
|
+
import { AuthProvider } from '../contexts/AuthContext';
|
|
121
|
+
|
|
122
|
+
export default function RootLayout() {
|
|
123
|
+
return (
|
|
124
|
+
<AuthProvider>
|
|
125
|
+
<Stack screenOptions={{ headerShown: false }} />
|
|
126
|
+
</AuthProvider>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## app/(auth)/login.tsx (exemplo mínimo)
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { View, Text, TextInput, Pressable } from 'react-native';
|
|
135
|
+
import { useAuth } from '../../contexts/AuthContext';
|
|
136
|
+
|
|
137
|
+
export default function Login() {
|
|
138
|
+
const { signIn, loading } = useAuth();
|
|
139
|
+
return (
|
|
140
|
+
<View style={{ padding: 24 }}>
|
|
141
|
+
<Text>Login</Text>
|
|
142
|
+
<Pressable onPress={() => signIn('a@b.com', 'password')} disabled={loading}>
|
|
143
|
+
<Text>Entrar</Text>
|
|
144
|
+
</Pressable>
|
|
145
|
+
</View>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Ajuste formulário com `react-hook-form` + `zod` conforme o SKILL.md. Use **`EXPO_PUBLIC_API_URL`** com IP da máquina em dispositivo físico.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: security-owasp
|
|
3
|
+
description: Checklist e implementações de segurança baseadas no OWASP Top 10 para NestJS + React. Use quando o desenvolvedor precisar revisar segurança, implementar autenticação segura, configurar proteções HTTP, validar inputs ou quando qualquer questão de segurança surgir no desenvolvimento.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Segurança — OWASP Top 10
|
|
7
|
+
|
|
8
|
+
Referência: https://owasp.org/Top10/
|
|
9
|
+
|
|
10
|
+
## Checklist rápido por módulo
|
|
11
|
+
|
|
12
|
+
### Ao criar qualquer módulo de autenticação
|
|
13
|
+
- [ ] **A01** — Guards em todas as rotas protegidas + RBAC
|
|
14
|
+
- [ ] **A02** — bcrypt salt >= 12, nunca retornar senha na resposta
|
|
15
|
+
- [ ] **A03** — DTO com class-validator, `whitelist: true` no ValidationPipe
|
|
16
|
+
- [ ] **A04** — Rate limiting com ThrottlerGuard (login: 5 tentativas/15min)
|
|
17
|
+
- [ ] **A05** — Helmet ativo, CORS explícito, secrets em `.env`
|
|
18
|
+
- [ ] **A07** — JWT access 15min + refresh 7d com rotation
|
|
19
|
+
- [ ] **A09** — Logs sem dados sensíveis (sem email, senha ou token)
|
|
20
|
+
|
|
21
|
+
### Ao criar qualquer endpoint de dados
|
|
22
|
+
- [ ] UUID nos IDs (evita enumeração — A01)
|
|
23
|
+
- [ ] Ownership validation (usuário só acessa seus dados)
|
|
24
|
+
- [ ] Soft delete (nunca delete físico em produção)
|
|
25
|
+
- [ ] Paginação (nunca retorne todos os registros)
|
|
26
|
+
|
|
27
|
+
## Implementações críticas
|
|
28
|
+
|
|
29
|
+
### A01 — Broken Access Control
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// Ownership validation — obrigatório em qualquer PATCH/DELETE
|
|
33
|
+
@Patch(':id')
|
|
34
|
+
@UseGuards(JwtAuthGuard)
|
|
35
|
+
async update(@Param('id') id: string, @CurrentUser() user: User) {
|
|
36
|
+
if (id !== user.id && user.role !== 'ADMIN') {
|
|
37
|
+
throw new ForbiddenException('Acesso negado');
|
|
38
|
+
}
|
|
39
|
+
// ...
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### A02 — Cryptographic Failures
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Sempre bcrypt com salt >= 12
|
|
47
|
+
const hash = await bcrypt.hash(password, 12);
|
|
48
|
+
const valid = await bcrypt.compare(plain, hash);
|
|
49
|
+
// Nunca MD5, SHA1, ou texto plano
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### A03 — Injection
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// DTO com validação — nunca confie no input do usuário
|
|
56
|
+
export class CreateUserDto {
|
|
57
|
+
@IsEmail() email: string;
|
|
58
|
+
@IsString() @MinLength(8) @MaxLength(128) password: string;
|
|
59
|
+
@IsString() @MaxLength(100) name: string;
|
|
60
|
+
}
|
|
61
|
+
// Prisma usa queries parametrizadas por padrão — nunca use $queryRaw com string
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### A07 — Identification Failures
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// Rate limit específico em rotas de auth
|
|
68
|
+
@Post('login')
|
|
69
|
+
@Throttle({ default: { ttl: 900000, limit: 5 } }) // 5 tentativas / 15min
|
|
70
|
+
login() {}
|
|
71
|
+
|
|
72
|
+
// Mesma mensagem para email/senha inválidos — evita user enumeration
|
|
73
|
+
throw new UnauthorizedException('Credenciais inválidas');
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Arquivos detalhados desta skill
|
|
77
|
+
|
|
78
|
+
- **owasp-full.md** — Todos os 10 itens com código completo e exemplos
|
|
79
|
+
|
|
80
|
+
Leia `owasp-full.md` para revisão aprofundada de segurança.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# OWASP Top 10 — referência estendida (NestJS + React)
|
|
2
|
+
|
|
3
|
+
Fonte: https://owasp.org/Top10/
|
|
4
|
+
|
|
5
|
+
Use com a skill **security** (`SKILL.md`) para checklist rápido. Aqui: um gancho por categoria + ação típica na stack DevSquad.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## A01 — Broken Access Control
|
|
10
|
+
|
|
11
|
+
- **Backend:** `@UseGuards(JwtAuthGuard)` + validar ownership em `PATCH`/`DELETE` (recurso pertence ao `user.id` ou role admin).
|
|
12
|
+
- **Frontend:** rotas privadas; não confiar só no UI — API deve negar.
|
|
13
|
+
|
|
14
|
+
## A02 — Cryptographic Failures
|
|
15
|
+
|
|
16
|
+
- Senhas: `bcrypt` salt ≥ 12; HTTPS em produção.
|
|
17
|
+
- Tokens: access curto; refresh com rotation; secrets só em env.
|
|
18
|
+
- **Mobile:** `expo-secure-store` para tokens — nunca AsyncStorage.
|
|
19
|
+
|
|
20
|
+
## A03 — Injection
|
|
21
|
+
|
|
22
|
+
- DTOs + `class-validator`; `ValidationPipe` com `whitelist: true`.
|
|
23
|
+
- Prisma: usar API tipada; evitar `$queryRaw` com concatenação de string.
|
|
24
|
+
|
|
25
|
+
## A04 — Insecure Design
|
|
26
|
+
|
|
27
|
+
- Threat modeling leve em features sensíveis (auth, pagamento, PII).
|
|
28
|
+
- Rate limit em login e endpoints caros.
|
|
29
|
+
|
|
30
|
+
## A05 — Security Misconfiguration
|
|
31
|
+
|
|
32
|
+
- `helmet`, CORS explícito, desativar stack traces ao cliente em prod.
|
|
33
|
+
- Headers e CSP conforme necessidade do front.
|
|
34
|
+
|
|
35
|
+
## A06 — Vulnerable and Outdated Components
|
|
36
|
+
|
|
37
|
+
- `npm audit` / dependabot; pin de versões críticas; atualizar LTS.
|
|
38
|
+
|
|
39
|
+
## A07 — Identification and Authentication Failures
|
|
40
|
+
|
|
41
|
+
- JWT access ~15m, refresh ~7d, rotation de refresh.
|
|
42
|
+
- Mensagem genérica em login falho (evitar enumeração de usuários).
|
|
43
|
+
|
|
44
|
+
## A08 — Software and Data Integrity Failures
|
|
45
|
+
|
|
46
|
+
- CI com lockfile; revisar scripts de deploy; assinatura de releases se aplicável.
|
|
47
|
+
|
|
48
|
+
## A09 — Security Logging and Monitoring Failures
|
|
49
|
+
|
|
50
|
+
- Logs estruturados sem PII/secrets; alertas em picos de 401/429.
|
|
51
|
+
|
|
52
|
+
## A10 — Server-Side Request Forgery (SSRF)
|
|
53
|
+
|
|
54
|
+
- Validar URLs permitidas em integrações que façam fetch server-side; allowlist de hosts.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## React (cliente)
|
|
59
|
+
|
|
60
|
+
- Não armazenar refresh em localStorage em apps de alto risco sem avaliação; preferir httpOnly cookie se o backend suportar.
|
|
61
|
+
- Sanitizar HTML se usar `dangerouslySetInnerHTML` (evitar XSS).
|
|
62
|
+
|
|
63
|
+
## Onde aprofundar no repositório
|
|
64
|
+
|
|
65
|
+
- NestJS: `nestjs/security.md`, `nestjs/auth.md`
|
|
66
|
+
- Skill **nestjs** + **database** para Prisma e multi-tenant com cuidado em A01.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# DevSquad
|
|
2
|
+
|
|
3
|
+
Você é o **DevSquad**, um agente especializado em inicializar e arquitetar sistemas profissionais.
|
|
4
|
+
|
|
5
|
+
## Stack de referência
|
|
6
|
+
- Backend: NestJS + Prisma + PostgreSQL
|
|
7
|
+
- Frontend: React + TypeScript + TailwindCSS
|
|
8
|
+
- Mobile: React Native + Expo + NativeWind
|
|
9
|
+
- Auth: JWT (access 15min + refresh 7d)
|
|
10
|
+
- Segurança: OWASP Top 10
|
|
11
|
+
- Versionamento: Git Flow + Conventional Commits
|
|
12
|
+
|
|
13
|
+
## Regras universais (sempre aplicar)
|
|
14
|
+
|
|
15
|
+
**Código**
|
|
16
|
+
- Nunca retorne senha em respostas de API
|
|
17
|
+
- IDs sempre em UUID (não sequencial)
|
|
18
|
+
- Variáveis sensíveis sempre em `.env`, nunca hardcoded
|
|
19
|
+
- DTOs com class-validator em todos os inputs
|
|
20
|
+
|
|
21
|
+
**Commits**
|
|
22
|
+
- Padrão: `tipo(escopo): descrição` — ex: `feat(auth): adicionar JWT`
|
|
23
|
+
- Branches: `feature/CK-{id}-{slug}` a partir do `develop`
|
|
24
|
+
|
|
25
|
+
**Modo guia (sempre ativo)**
|
|
26
|
+
Ao gerar qualquer código, explique a decisão com este padrão:
|
|
27
|
+
```
|
|
28
|
+
✅ O QUÊ — [o que está sendo criado]
|
|
29
|
+
📖 POR QUÊ — [razão técnica]
|
|
30
|
+
🔒 SEGURANÇA — [referência OWASP se aplicável]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Onboarding de novo projeto
|
|
34
|
+
|
|
35
|
+
Ao receber `/devsquad init`, faça estas 10 perguntas antes de gerar qualquer coisa:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
1. Nome do projeto?
|
|
39
|
+
2. Web, mobile ou ambos?
|
|
40
|
+
3. Qual problema resolve? (1-3 frases)
|
|
41
|
+
4. Quais são os perfis de usuário? (ex: admin, cliente)
|
|
42
|
+
5. Terá autenticação social? (Google, GitHub...)
|
|
43
|
+
6. Terá upload de arquivos?
|
|
44
|
+
7. Terá pagamentos?
|
|
45
|
+
8. Terá notificações? (email, push, SMS)
|
|
46
|
+
9. Solo ou equipe?
|
|
47
|
+
10. Já tem protótipo no Figma ou vai criar do zero?
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Após as respostas, gere: estrutura de pastas, checklist de setup, tasks do ClickUp por sprint e branches Git iniciais.
|
|
51
|
+
|
|
52
|
+
## Skills disponíveis
|
|
53
|
+
|
|
54
|
+
Carregue a skill correspondente conforme a necessidade do desenvolvedor:
|
|
55
|
+
|
|
56
|
+
- `/devsquad backend` → `.claude/skills/nestjs/`
|
|
57
|
+
- `/devsquad frontend` → `.claude/skills/react/`
|
|
58
|
+
- `/devsquad mobile` → `.claude/skills/react-native/`
|
|
59
|
+
- `/devsquad git` → `.claude/skills/git/`
|
|
60
|
+
- `/devsquad security` → `.claude/skills/security/`
|
|
61
|
+
- `/devsquad database` → `.claude/skills/database/`
|
|
62
|
+
- `/devsquad postman` → `.claude/skills/postman/`
|
|
63
|
+
- `/devsquad docs` → `.claude/skills/docs/`
|
|
64
|
+
- `/devsquad clickup` → `.claude/skills/clickup/`
|
|
65
|
+
- `/devsquad figma` → `.claude/skills/figma/`
|