outlet-orm 4.1.0 → 4.2.1
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/README.md +46 -7
- package/bin/init.js +190 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,30 +32,69 @@ Si aucun driver n'est installé, un message d'erreur explicite vous indiquera le
|
|
|
32
32
|
|
|
33
33
|
Organisez votre projet utilisant Outlet ORM comme suit :
|
|
34
34
|
|
|
35
|
+
> 🔐 **Sécurité** : Voir le [Guide de Sécurité](./docs/SECURITY.md) pour les bonnes pratiques.
|
|
36
|
+
|
|
35
37
|
```
|
|
36
38
|
mon-projet/
|
|
37
|
-
├── .env #
|
|
39
|
+
├── .env # ⚠️ JAMAIS commité (dans .gitignore)
|
|
40
|
+
├── .env.example # Template sans secrets
|
|
41
|
+
├── .gitignore # Exclure .env, node_modules, logs
|
|
38
42
|
├── package.json
|
|
43
|
+
├── config/ # 🔒 Configuration centralisée
|
|
44
|
+
│ ├── app.js # Config générale
|
|
45
|
+
│ ├── database.js # Config DB (lit .env)
|
|
46
|
+
│ └── security.js # Rate limit, helmet, CORS...
|
|
39
47
|
├── database/
|
|
40
48
|
│ ├── config.js # Config migrations (généré par outlet-init)
|
|
41
49
|
│ └── migrations/ # Vos fichiers de migration
|
|
42
|
-
│ ├── 20240101_create_users_table.js
|
|
43
|
-
│ └── 20240102_create_posts_table.js
|
|
44
50
|
├── models/ # Vos classes Model
|
|
45
51
|
│ ├── User.js
|
|
46
52
|
│ ├── Post.js
|
|
47
53
|
│ └── Comment.js
|
|
54
|
+
├── controllers/ # Vos contrôleurs (logique métier)
|
|
55
|
+
│ ├── UserController.js
|
|
56
|
+
│ └── PostController.js
|
|
57
|
+
├── routes/ # Vos fichiers de routes
|
|
58
|
+
│ ├── index.js
|
|
59
|
+
│ └── userRoutes.js
|
|
60
|
+
├── middlewares/ # 🔒 Middlewares de sécurité
|
|
61
|
+
│ ├── auth.js # Authentification JWT
|
|
62
|
+
│ ├── authorization.js # Contrôle des permissions (RBAC)
|
|
63
|
+
│ ├── rateLimiter.js # Protection anti-DDoS
|
|
64
|
+
│ ├── validator.js # Validation des entrées
|
|
65
|
+
│ └── errorHandler.js # Gestion centralisée des erreurs
|
|
66
|
+
├── services/ # Vos services (logique réutilisable)
|
|
67
|
+
│ ├── AuthService.js
|
|
68
|
+
│ └── EmailService.js
|
|
69
|
+
├── utils/ # 🔒 Utilitaires sécurité
|
|
70
|
+
│ ├── hash.js # Hachage mots de passe (bcrypt)
|
|
71
|
+
│ └── token.js # Génération tokens sécurisés
|
|
72
|
+
├── validators/ # 🔒 Schémas de validation
|
|
73
|
+
│ └── userValidator.js
|
|
74
|
+
├── public/ # ✅ Seul dossier accessible publiquement
|
|
75
|
+
│ ├── images/
|
|
76
|
+
│ ├── css/
|
|
77
|
+
│ └── js/
|
|
78
|
+
├── uploads/ # ⚠️ Fichiers uploadés (validés)
|
|
79
|
+
├── logs/ # 📋 Journaux (non versionnés)
|
|
48
80
|
├── src/ # Votre code applicatif
|
|
49
81
|
│ └── index.js
|
|
50
82
|
└── tests/ # Vos tests
|
|
51
83
|
└── models.test.js
|
|
52
84
|
```
|
|
53
85
|
|
|
54
|
-
| Dossier | Rôle |
|
|
86
|
+
| Dossier | Rôle | Sécurité |
|
|
55
87
|
|---------|------|----------|
|
|
56
|
-
| `
|
|
57
|
-
| `database
|
|
58
|
-
| `models/` |
|
|
88
|
+
| `config/` | Configuration centralisée | 🔒 Lit les secrets depuis .env |
|
|
89
|
+
| `database/` | Migrations et config DB | `outlet-init` |
|
|
90
|
+
| `models/` | Classes Model avec `hidden` et `fillable` | 🔒 Mass assignment protection |
|
|
91
|
+
| `controllers/` | Logique métier | Valider les entrées |
|
|
92
|
+
| `routes/` | Définition des routes API/Web | 🔒 Appliquer middlewares auth |
|
|
93
|
+
| `middlewares/` | Auth, validation, rate limiting | 🔒 **Critique pour la sécurité** |
|
|
94
|
+
| `services/` | Services réutilisables | Isoler la logique sensible |
|
|
95
|
+
| `utils/` | Hash, tokens, encryption | 🔒 Ne jamais exposer |
|
|
96
|
+
| `public/` | Seul dossier accessible | ✅ Fichiers statiques sûrs |
|
|
97
|
+
| `logs/` | Journaux d'accès/erreurs | 📋 Dans .gitignore |
|
|
59
98
|
|
|
60
99
|
## ✨ Fonctionnalités clés
|
|
61
100
|
|
package/bin/init.js
CHANGED
|
@@ -93,6 +93,190 @@ module.exports = db;
|
|
|
93
93
|
fs.writeFileSync(configPath, configContent);
|
|
94
94
|
console.log(`\n✅ Fichier de configuration créé: ${configPath}`);
|
|
95
95
|
|
|
96
|
+
// Create project structure directories
|
|
97
|
+
const directories = [
|
|
98
|
+
'config',
|
|
99
|
+
'database',
|
|
100
|
+
'database/migrations',
|
|
101
|
+
'models',
|
|
102
|
+
'controllers',
|
|
103
|
+
'routes',
|
|
104
|
+
'middlewares',
|
|
105
|
+
'services',
|
|
106
|
+
'utils',
|
|
107
|
+
'validators',
|
|
108
|
+
'public',
|
|
109
|
+
'public/images',
|
|
110
|
+
'public/css',
|
|
111
|
+
'public/js',
|
|
112
|
+
'uploads',
|
|
113
|
+
'logs',
|
|
114
|
+
'src',
|
|
115
|
+
'tests'
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
console.log('\n📁 Création de la structure de projet...');
|
|
119
|
+
for (const dir of directories) {
|
|
120
|
+
const dirPath = path.join(process.cwd(), dir);
|
|
121
|
+
if (!fs.existsSync(dirPath)) {
|
|
122
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
123
|
+
console.log(` ✅ ${dir}/`);
|
|
124
|
+
} else {
|
|
125
|
+
console.log(` ⏭️ ${dir}/ (existe déjà)`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Generate .gitignore
|
|
130
|
+
const gitignoreContent = `# Secrets
|
|
131
|
+
.env
|
|
132
|
+
.env.local
|
|
133
|
+
.env.production
|
|
134
|
+
|
|
135
|
+
# Logs
|
|
136
|
+
logs/
|
|
137
|
+
*.log
|
|
138
|
+
|
|
139
|
+
# Uploads
|
|
140
|
+
uploads/
|
|
141
|
+
|
|
142
|
+
# Dependencies
|
|
143
|
+
node_modules/
|
|
144
|
+
|
|
145
|
+
# Build
|
|
146
|
+
dist/
|
|
147
|
+
build/
|
|
148
|
+
|
|
149
|
+
# IDE
|
|
150
|
+
.vscode/
|
|
151
|
+
.idea/
|
|
152
|
+
`;
|
|
153
|
+
|
|
154
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
155
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
156
|
+
fs.writeFileSync(gitignorePath, gitignoreContent);
|
|
157
|
+
console.log(`\n✅ .gitignore créé`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Generate .env.example
|
|
161
|
+
const envExampleContent = `# Base de données
|
|
162
|
+
DB_DRIVER=mysql
|
|
163
|
+
DB_HOST=localhost
|
|
164
|
+
DB_PORT=3306
|
|
165
|
+
DB_DATABASE=myapp
|
|
166
|
+
DB_USER=your_user
|
|
167
|
+
DB_PASSWORD=your_password
|
|
168
|
+
|
|
169
|
+
# Sécurité
|
|
170
|
+
JWT_SECRET=your_jwt_secret_here
|
|
171
|
+
JWT_EXPIRES_IN=15m
|
|
172
|
+
|
|
173
|
+
# Application
|
|
174
|
+
NODE_ENV=development
|
|
175
|
+
PORT=3000
|
|
176
|
+
|
|
177
|
+
# CORS
|
|
178
|
+
CORS_ORIGIN=http://localhost:3000
|
|
179
|
+
`;
|
|
180
|
+
|
|
181
|
+
const envExamplePath = path.join(process.cwd(), '.env.example');
|
|
182
|
+
if (!fs.existsSync(envExamplePath)) {
|
|
183
|
+
fs.writeFileSync(envExamplePath, envExampleContent);
|
|
184
|
+
console.log(`✅ .env.example créé`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Generate config/security.js
|
|
188
|
+
const securityConfigContent = `/**
|
|
189
|
+
* Configuration de sécurité
|
|
190
|
+
* npm install helmet express-rate-limit xss-clean hpp
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
// Rate limiting (100 requêtes/15min par IP)
|
|
195
|
+
rateLimit: {
|
|
196
|
+
windowMs: 15 * 60 * 1000,
|
|
197
|
+
max: 100,
|
|
198
|
+
message: { error: 'Trop de requêtes, réessayez plus tard' }
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
// Rate limiting strict pour auth (5 tentatives/15min)
|
|
202
|
+
authRateLimit: {
|
|
203
|
+
windowMs: 15 * 60 * 1000,
|
|
204
|
+
max: 5,
|
|
205
|
+
message: { error: 'Trop de tentatives de connexion' }
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
// CORS
|
|
209
|
+
cors: {
|
|
210
|
+
origin: process.env.CORS_ORIGIN || 'http://localhost:3000',
|
|
211
|
+
credentials: true,
|
|
212
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
|
213
|
+
allowedHeaders: ['Content-Type', 'Authorization']
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
// JWT
|
|
217
|
+
jwt: {
|
|
218
|
+
secret: process.env.JWT_SECRET,
|
|
219
|
+
expiresIn: process.env.JWT_EXPIRES_IN || '15m'
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
`;
|
|
223
|
+
|
|
224
|
+
const securityConfigPath = path.join(process.cwd(), 'config', 'security.js');
|
|
225
|
+
if (!fs.existsSync(securityConfigPath)) {
|
|
226
|
+
fs.writeFileSync(securityConfigPath, securityConfigContent);
|
|
227
|
+
console.log(`✅ config/security.js créé`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Generate middlewares/errorHandler.js
|
|
231
|
+
const errorHandlerContent = `/**
|
|
232
|
+
* Gestionnaire d'erreurs centralisé
|
|
233
|
+
*/
|
|
234
|
+
const errorHandler = (err, req, res, next) => {
|
|
235
|
+
console.error(\`[\${new Date().toISOString()}] Error:\`, err);
|
|
236
|
+
|
|
237
|
+
const isDev = process.env.NODE_ENV === 'development';
|
|
238
|
+
|
|
239
|
+
res.status(err.status || 500).json({
|
|
240
|
+
error: isDev ? err.message : 'Erreur serveur',
|
|
241
|
+
stack: isDev ? err.stack : undefined
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
module.exports = errorHandler;
|
|
246
|
+
`;
|
|
247
|
+
|
|
248
|
+
const errorHandlerPath = path.join(process.cwd(), 'middlewares', 'errorHandler.js');
|
|
249
|
+
if (!fs.existsSync(errorHandlerPath)) {
|
|
250
|
+
fs.writeFileSync(errorHandlerPath, errorHandlerContent);
|
|
251
|
+
console.log(`✅ middlewares/errorHandler.js créé`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Generate utils/hash.js
|
|
255
|
+
const hashUtilContent = `/**
|
|
256
|
+
* Utilitaires de hachage
|
|
257
|
+
* npm install bcrypt
|
|
258
|
+
*/
|
|
259
|
+
const bcrypt = require('bcrypt');
|
|
260
|
+
|
|
261
|
+
const SALT_ROUNDS = 12;
|
|
262
|
+
|
|
263
|
+
const hashPassword = async (password) => {
|
|
264
|
+
return bcrypt.hash(password, SALT_ROUNDS);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const verifyPassword = async (password, hash) => {
|
|
268
|
+
return bcrypt.compare(password, hash);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
module.exports = { hashPassword, verifyPassword };
|
|
272
|
+
`;
|
|
273
|
+
|
|
274
|
+
const hashUtilPath = path.join(process.cwd(), 'utils', 'hash.js');
|
|
275
|
+
if (!fs.existsSync(hashUtilPath)) {
|
|
276
|
+
fs.writeFileSync(hashUtilPath, hashUtilContent);
|
|
277
|
+
console.log(`✅ utils/hash.js créé`);
|
|
278
|
+
}
|
|
279
|
+
|
|
96
280
|
// Generate example model
|
|
97
281
|
const modelContent = `const { Model } = require('outlet-orm');
|
|
98
282
|
const db = require('./database');
|
|
@@ -100,11 +284,16 @@ const db = require('./database');
|
|
|
100
284
|
class User extends Model {
|
|
101
285
|
static table = 'users';
|
|
102
286
|
static fillable = ['name', 'email', 'password'];
|
|
103
|
-
static hidden = ['password'];
|
|
287
|
+
static hidden = ['password', 'refresh_token']; // 🔒 Ne jamais exposer
|
|
104
288
|
static casts = {
|
|
105
289
|
id: 'int',
|
|
106
290
|
email_verified: 'boolean'
|
|
107
291
|
};
|
|
292
|
+
static rules = {
|
|
293
|
+
name: 'required|string|min:2|max:100',
|
|
294
|
+
email: 'required|email',
|
|
295
|
+
password: 'required|min:8'
|
|
296
|
+
};
|
|
108
297
|
static connection = db;
|
|
109
298
|
|
|
110
299
|
// Définissez vos relations ici
|