data-primals-engine 1.4.0 → 1.4.2
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 +856 -797
- package/client/src/AssistantChat.jsx +48 -5
- package/client/src/AssistantChat.scss +0 -1
- package/client/src/ChartConfigModal.jsx +34 -9
- package/client/src/Dashboard.jsx +396 -349
- package/client/src/DashboardChart.jsx +91 -12
- package/client/src/DataTable.jsx +817 -807
- package/client/src/Field.jsx +1784 -1782
- package/client/src/PackGallery.jsx +391 -290
- package/client/src/PackGallery.scss +231 -210
- package/client/src/contexts/UIContext.jsx +5 -2
- package/client/src/translations.js +188 -0
- package/package.json +19 -8
- package/src/config.js +2 -2
- package/src/core.js +21 -3
- package/src/engine.js +2 -1
- package/src/events.js +4 -3
- package/src/gameObject.js +1 -1
- package/src/middlewares/middleware-mongodb.js +3 -1
- package/src/modules/assistant/assistant.js +131 -42
- package/src/modules/auth-google/index.js +51 -0
- package/src/modules/auth-microsoft/index.js +82 -0
- package/src/modules/auth-saml/index.js +90 -0
- package/src/modules/bucket.js +3 -2
- package/src/modules/data/data.backup.js +374 -0
- package/src/modules/data/data.history.js +11 -8
- package/src/modules/data/data.js +190 -4543
- package/src/modules/data/data.operations.js +2790 -0
- package/src/modules/data/data.relations.js +687 -0
- package/src/modules/data/data.routes.js +1785 -1646
- package/src/modules/data/data.scheduling.js +274 -0
- package/src/modules/data/data.validation.js +245 -0
- package/src/modules/data/index.js +29 -1
- package/src/modules/user.js +2 -1
- package/src/modules/workflow.js +3 -2
- package/src/providers.js +110 -1
- package/src/services/stripe.js +3 -2
- package/src/sso.js +194 -0
- package/test/data.integration.test.js +3 -0
- package/test/user.test.js +1 -1
package/src/providers.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {compare} from "bcrypt";
|
|
1
|
+
import {compare, hash} from "bcrypt";
|
|
2
2
|
import {maxTotalPrivateFilesSize} from "./constants.js";
|
|
3
|
+
import {getCollection, ObjectId} from "./modules/mongodb.js";
|
|
4
|
+
import {Logger} from "./gameObject.js";
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* @class UserProvider
|
|
@@ -18,6 +20,16 @@ export class UserProvider {
|
|
|
18
20
|
this.plans = this.getUserPlans();
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Trouve un utilisateur par son ID unique. Nécessaire pour la désérialisation de Passport.
|
|
25
|
+
* @param {string} id - L'ID de l'utilisateur.
|
|
26
|
+
* @returns {Promise<object|null>} L'objet utilisateur ou null.
|
|
27
|
+
*/
|
|
28
|
+
async findUserById(id) {
|
|
29
|
+
throw new Error("Method 'findUserById()' must be implemented.");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
21
33
|
/**
|
|
22
34
|
* Met à jour un nouvel utilisateur.
|
|
23
35
|
* @param user
|
|
@@ -171,4 +183,101 @@ export class DefaultUserProvider extends UserProvider {
|
|
|
171
183
|
}
|
|
172
184
|
}
|
|
173
185
|
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @class MongoUserProvider
|
|
190
|
+
* Un fournisseur d'utilisateurs prêt pour la production qui stocke les utilisateurs
|
|
191
|
+
* dans MongoDB et gère les mots de passe de manière sécurisée avec bcrypt.
|
|
192
|
+
*/
|
|
193
|
+
export class MongoUserProvider extends UserProvider {
|
|
194
|
+
constructor(engine) {
|
|
195
|
+
super(engine);
|
|
196
|
+
this.usersCollection = getCollection("users");
|
|
197
|
+
this.logger = engine.getComponent(Logger);
|
|
198
|
+
this.logger.info("MongoUserProvider initialized, using 'users' collection for local accounts.");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Trouve un utilisateur par son ID MongoDB.
|
|
203
|
+
* @param {string} id - L'ID de l'utilisateur.
|
|
204
|
+
* @returns {Promise<object|null>}
|
|
205
|
+
*/
|
|
206
|
+
async findUserById(id) {
|
|
207
|
+
try {
|
|
208
|
+
if (!ObjectId.isValid(id)) return null;
|
|
209
|
+
// On exclut le mot de passe des requêtes pour des raisons de sécurité
|
|
210
|
+
return await this.usersCollection.findOne({ _id: new ObjectId(id) }, { projection: { password: 0 } });
|
|
211
|
+
} catch (e) {
|
|
212
|
+
this.logger.error(`Error in findUserById: ${e.message}`);
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Trouve un utilisateur par son nom d'utilisateur ou son email.
|
|
219
|
+
* @param {string} username - Le nom d'utilisateur ou l'email.
|
|
220
|
+
* @returns {Promise<object|null>}
|
|
221
|
+
*/
|
|
222
|
+
async findUserByUsername(username) {
|
|
223
|
+
// Permet de se connecter avec l'username ou l'email
|
|
224
|
+
return await this.usersCollection.findOne({ $or: [{ username }, { email: username }] });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Valide le mot de passe fourni par rapport au hash stocké.
|
|
229
|
+
* @param {object} user - L'objet utilisateur complet (avec le hash du mot de passe).
|
|
230
|
+
* @param {string} password - Le mot de passe en clair à vérifier.
|
|
231
|
+
* @returns {Promise<boolean>}
|
|
232
|
+
*/
|
|
233
|
+
async validatePassword(user, password) {
|
|
234
|
+
if (!user || !user.password || !password) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
return await compare(password, user.password);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Crée un nouvel utilisateur avec un mot de passe haché.
|
|
242
|
+
* @param {object} userData - Données de l'utilisateur (username, email, password, etc.).
|
|
243
|
+
* @returns {Promise<object>} L'utilisateur nouvellement créé (sans le mot de passe).
|
|
244
|
+
*/
|
|
245
|
+
async createUser(userData) {
|
|
246
|
+
const { username, email, password, ...otherData } = userData;
|
|
247
|
+
if (!username || !email || !password) {
|
|
248
|
+
throw new Error("Username, email, and password are required to create a user.");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const existingUser = await this.usersCollection.findOne({ $or: [{ username }, { email }] });
|
|
252
|
+
if (existingUser) {
|
|
253
|
+
throw new Error("User with this username or email already exists.");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const hashedPassword = await hash(password, 12);
|
|
257
|
+
|
|
258
|
+
const result = await this.usersCollection.insertOne({
|
|
259
|
+
username,
|
|
260
|
+
email,
|
|
261
|
+
password: hashedPassword,
|
|
262
|
+
createdAt: new Date(),
|
|
263
|
+
userPlan: 'free', // Plan par défaut
|
|
264
|
+
...otherData
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return await this.findUserById(result.insertedId);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Initialise l'utilisateur sur la requête à partir de la session.
|
|
272
|
+
* Ne gère que les utilisateurs locaux. Le SSO est géré par Passport.
|
|
273
|
+
* @param {object} req - L'objet requête Express.
|
|
274
|
+
*/
|
|
275
|
+
async initiateUser(req) {
|
|
276
|
+
if (req.session?.user?._id) {
|
|
277
|
+
const user = await this.findUserById(req.session.user._id);
|
|
278
|
+
if (user) {
|
|
279
|
+
req.me = user; // Attache l'utilisateur à la requête
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
174
283
|
}
|
package/src/services/stripe.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Stripe from 'stripe';
|
|
2
2
|
import {editData, insertData, searchData} from '../modules/data/index.js';
|
|
3
3
|
import {getEnv} from "../modules/user.js";
|
|
4
|
+
import {safeAssignObject} from "../core.js";
|
|
4
5
|
|
|
5
6
|
const stripeInstances = {};
|
|
6
7
|
|
|
@@ -15,10 +16,10 @@ async function getStripeClient(user) {
|
|
|
15
16
|
if (!stripeInstances[userId]) {
|
|
16
17
|
const env = await getEnv(user);
|
|
17
18
|
if (env.STRIPE_SECRET_KEY) {
|
|
18
|
-
stripeInstances
|
|
19
|
+
safeAssignObject(stripeInstances, userId, {
|
|
19
20
|
client: new Stripe(env.STRIPE_SECRET_KEY),
|
|
20
21
|
webhookSecret: env.STRIPE_WEBHOOK_SECRET
|
|
21
|
-
};
|
|
22
|
+
});
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
return stripeInstances[userId] || null;
|
package/src/sso.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import passport from 'passport';
|
|
2
|
+
import session from 'express-session';
|
|
3
|
+
import { Behaviour } from './gameObject.js';
|
|
4
|
+
import { cookiesSecret } from "./constants.js";
|
|
5
|
+
import {UserProvider} from "./providers.js";
|
|
6
|
+
import {getCollection} from "./modules/mongodb.js";
|
|
7
|
+
import process from "process";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @class SSOUserProvider
|
|
11
|
+
* Gère les utilisateurs provenant de fournisseurs d'identité externes (SSO).
|
|
12
|
+
* Sa responsabilité principale est de trouver un utilisateur existant correspondant
|
|
13
|
+
* au profil SSO ou d'en créer un nouveau si nécessaire (provisioning à la volée).
|
|
14
|
+
*/
|
|
15
|
+
export class SSOUserProvider extends UserProvider {
|
|
16
|
+
|
|
17
|
+
usersCollection;
|
|
18
|
+
|
|
19
|
+
constructor(engine) {
|
|
20
|
+
super(engine);
|
|
21
|
+
// Idéalement, la collection d'utilisateurs devrait être centralisée.
|
|
22
|
+
// Pour cet exemple, nous la récupérons directement.
|
|
23
|
+
this.usersCollection = getCollection("users"); // Assurez-vous que cette collection existe
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Trouve un utilisateur par son ID. Nécessaire pour la désérialisation de Passport.
|
|
28
|
+
* @param {string} id - L'ID de l'utilisateur.
|
|
29
|
+
* @returns {Promise<object|null>}
|
|
30
|
+
*/
|
|
31
|
+
async findUserById(id) {
|
|
32
|
+
// Note: Assurez-vous que vos utilisateurs ont un champ `_id` stable.
|
|
33
|
+
// Si vous utilisez le `username` comme identifiant principal, adaptez cette méthode.
|
|
34
|
+
try {
|
|
35
|
+
const { ObjectId } = await import('mongodb');
|
|
36
|
+
return await this.usersCollection.findOne({ _id: new ObjectId(id) });
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return null; // Gère le cas où l'ID n'est pas un ObjectId valide
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* La méthode clé pour l'intégration SSO.
|
|
44
|
+
* Cherche un utilisateur basé sur le profil fourni par le fournisseur d'identité.
|
|
45
|
+
* S'il n'existe pas, il le crée.
|
|
46
|
+
* @param {object} profile - Le profil utilisateur de Passport (ex: de Google, SAML).
|
|
47
|
+
* @returns {Promise<object>} L'utilisateur de votre système.
|
|
48
|
+
*/
|
|
49
|
+
async findOrCreate(profile) {
|
|
50
|
+
if (!profile.emails || !profile.emails[0] || !profile.emails[0].value) {
|
|
51
|
+
throw new Error("Le profil du fournisseur d'identité ne contient pas d'email.");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const email = profile.emails[0].value;
|
|
55
|
+
|
|
56
|
+
// 1. Chercher un utilisateur existant avec cet email.
|
|
57
|
+
const existingUser = await this.usersCollection.findOne({ email: email });
|
|
58
|
+
|
|
59
|
+
if (existingUser) {
|
|
60
|
+
// TODO: Mettre à jour les informations de l'utilisateur si nécessaire (ex: nom, photo de profil)
|
|
61
|
+
// await this.usersCollection.updateOne({ _id: existingUser._id }, { $set: { lastLogin: new Date() } });
|
|
62
|
+
return existingUser;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 2. Si aucun utilisateur n'est trouvé, en créer un nouveau.
|
|
66
|
+
const newUser = {
|
|
67
|
+
username: email, // Utiliser l'email comme nom d'utilisateur par défaut
|
|
68
|
+
email: email,
|
|
69
|
+
name: profile.displayName || email,
|
|
70
|
+
provider: profile.provider, // ex: 'google', 'saml'
|
|
71
|
+
providerId: profile.id,
|
|
72
|
+
userPlan: 'free', // Assigner un plan par défaut
|
|
73
|
+
createdAt: new Date()
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const result = await this.usersCollection.insertOne(newUser);
|
|
77
|
+
return await this.findUserById(result.insertedId);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Les autres méthodes comme validatePassword ne sont pas pertinentes pour le SSO.
|
|
81
|
+
// On peut les laisser vides ou les faire lever une erreur.
|
|
82
|
+
async validatePassword(user, password) { return false; }
|
|
83
|
+
async findUserByUsername(username) { return await this.usersCollection.findOne({ username }); }
|
|
84
|
+
async initiateUser(req) { /* Géré par Passport */ }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @class Sso
|
|
89
|
+
* @extends Behaviour
|
|
90
|
+
* Un composant réutilisable qui encapsule la logique de Passport.js pour l'authentification.
|
|
91
|
+
* Il gère la session, la sérialisation, et permet à d'autres modules d'enregistrer
|
|
92
|
+
* dynamiquement des stratégies d'authentification (Google, SAML, etc.).
|
|
93
|
+
*/
|
|
94
|
+
export class Sso extends Behaviour {
|
|
95
|
+
#ssoUserProvider = null;
|
|
96
|
+
#logger = null;
|
|
97
|
+
|
|
98
|
+
constructor(gameObject) {
|
|
99
|
+
super(gameObject);
|
|
100
|
+
this.strategies = new Map();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Initialise le composant avec les middlewares et la configuration de base de Passport.
|
|
105
|
+
* @param {object} options
|
|
106
|
+
* @param {UserProvider} options.ssoUserProvider - Le provider pour trouver ou créer des utilisateurs SSO.
|
|
107
|
+
*/
|
|
108
|
+
initialize({ ssoUserProvider }) {
|
|
109
|
+
if (!ssoUserProvider) {
|
|
110
|
+
throw new Error("PassportAuth component requires an ssoUserProvider to be initialized.");
|
|
111
|
+
}
|
|
112
|
+
this.#ssoUserProvider = ssoUserProvider;
|
|
113
|
+
this.#logger = this.gameObject.getComponent('Logger'); // Assumant que Logger est un composant
|
|
114
|
+
|
|
115
|
+
const app = this.gameObject; // this.gameObject est l'instance de l'engine (Express app)
|
|
116
|
+
|
|
117
|
+
// 1. Configurer la session Express (prérequis pour Passport)
|
|
118
|
+
app.use(session({
|
|
119
|
+
secret: process.env.COOKIES_SECRET || cookiesSecret,
|
|
120
|
+
resave: false,
|
|
121
|
+
saveUninitialized: false,
|
|
122
|
+
cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, sameSite: 'lax' }
|
|
123
|
+
}));
|
|
124
|
+
|
|
125
|
+
// 2. Initialiser Passport
|
|
126
|
+
app.use(passport.initialize());
|
|
127
|
+
app.use(passport.session());
|
|
128
|
+
|
|
129
|
+
// 3. Configurer la sérialisation/désérialisation
|
|
130
|
+
passport.serializeUser((user, done) => {
|
|
131
|
+
done(null, user._id);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
passport.deserializeUser(async (id, done) => {
|
|
135
|
+
try {
|
|
136
|
+
const user = await this.#ssoUserProvider.findUserById(id);
|
|
137
|
+
done(null, user);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
done(err);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
this.#logger.info("PassportAuth component initialized successfully.");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Permet à d'autres modules d'enregistrer une nouvelle stratégie d'authentification.
|
|
148
|
+
* @param {string} name - Le nom de la stratégie (ex: 'google', 'saml').
|
|
149
|
+
* @param {object} strategy - L'instance de la stratégie Passport.
|
|
150
|
+
* @param {object} routes - Les chemins pour l'authentification.
|
|
151
|
+
* @param {string} routes.authPath - Le chemin pour initier l'authentification (ex: '/api/auth/google').
|
|
152
|
+
* @param {string} routes.callbackPath - Le chemin de callback (ex: '/api/auth/google/callback').
|
|
153
|
+
* @param {object} options - Options d'authentification pour Passport (ex: { scope: ['profile', 'email'] }).
|
|
154
|
+
*/
|
|
155
|
+
addStrategy(name, strategy, routes, options = {}) {
|
|
156
|
+
if (this.strategies.has(name)) {
|
|
157
|
+
this.#logger.warn(`Passport strategy '${name}' is already registered. Overwriting.`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Enregistrer la stratégie auprès de Passport
|
|
161
|
+
passport.use(strategy);
|
|
162
|
+
|
|
163
|
+
const app = this.gameObject;
|
|
164
|
+
|
|
165
|
+
// Créer la route d'initiation
|
|
166
|
+
app.get(routes.authPath, passport.authenticate(name, options));
|
|
167
|
+
|
|
168
|
+
// Créer la route de callback
|
|
169
|
+
app.get(
|
|
170
|
+
routes.callbackPath,
|
|
171
|
+
passport.authenticate(name, { failureRedirect: '/login', failureMessage: true }),
|
|
172
|
+
(req, res) => {
|
|
173
|
+
// Redirection après une authentification réussie
|
|
174
|
+
res.redirect(req.session.returnTo || '/dashboard');
|
|
175
|
+
delete req.session.returnTo;
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
this.strategies.set(name, { strategy, routes, options });
|
|
180
|
+
this.#logger.info(`Passport strategy '${name}' registered with routes: ${routes.authPath}, ${routes.callbackPath}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
addLogoutRoute(path = '/api/auth/logout') {
|
|
184
|
+
this.gameObject.get(path, (req, res, next) => {
|
|
185
|
+
req.logout((err) => {
|
|
186
|
+
if (err) { return next(err); }
|
|
187
|
+
req.session.destroy(() => {
|
|
188
|
+
res.redirect('/');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
this.#logger.info(`Logout route registered at '${path}'.`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -699,6 +699,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
699
699
|
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition, purge } = await setupTestContext();
|
|
700
700
|
// 1. Définir le pack de données mock
|
|
701
701
|
const mockPack = {
|
|
702
|
+
private: false,
|
|
702
703
|
name: 'test-pack-for-install',
|
|
703
704
|
models: [
|
|
704
705
|
{
|
|
@@ -774,6 +775,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
774
775
|
const { currentTestUser, comprehensiveTestModelDefinition, relatedModelDefinition, purge } = await setupTestContext();
|
|
775
776
|
const mockPackWithInvalidModel = {
|
|
776
777
|
name: 'invalid-model-pack',
|
|
778
|
+
private: false,
|
|
777
779
|
models: [
|
|
778
780
|
{
|
|
779
781
|
// 'name' est requis, on l'omet pour causer une erreur
|
|
@@ -816,6 +818,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
|
|
|
816
818
|
// 1. Définir le pack avec des données JSON
|
|
817
819
|
const mockPackWithJson = {
|
|
818
820
|
name: 'test-pack-with-json',
|
|
821
|
+
private: false,
|
|
819
822
|
models: [
|
|
820
823
|
{
|
|
821
824
|
name: 'packConfig',
|
package/test/user.test.js
CHANGED
|
@@ -66,7 +66,7 @@ describe('User Permission Logic', () => {
|
|
|
66
66
|
|
|
67
67
|
// --- SETUP : Création des données de test avant tous les tests ---
|
|
68
68
|
beforeAll(async () => {
|
|
69
|
-
Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant"]);
|
|
69
|
+
Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant", "auth-google", "auth-saml", "auth-microsoft"]);
|
|
70
70
|
engine = await initEngine();
|
|
71
71
|
|
|
72
72
|
});
|