@raclettejs/core 0.1.23 → 0.1.25
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/CHANGELOG.md +41 -0
- package/bin/cli.js +2 -2
- package/dist/cli.js +136 -155
- package/dist/cli.js.map +4 -4
- package/dist/index.js +15 -16
- package/dist/index.js.map +4 -4
- package/package.json +24 -22
- package/services/backend/.yarn/install-state.gz +0 -0
- package/services/backend/dist/core/eventBus/index.js +7 -0
- package/services/backend/dist/core/pluginSystem/configGenerator/index.js +346 -0
- package/services/backend/dist/core/sockets/index.js +95 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/account/events/index.js +31 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/account/index.js +48 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/account/routes/index.js +15 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/composition/events/index.js +22 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/composition/index.js +51 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/composition/routes/index.js +19 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/interactionLink/events/index.js +22 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/interactionLink/index.js +48 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/index.js +19 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/project/events/index.js +31 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/project/index.js +49 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/project/routes/index.js +16 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/tag/events/index.js +39 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/tag/index.js +50 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/tag/routes/index.js +19 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/user/events/index.js +31 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/user/index.js +51 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/user/routes/index.js +21 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/workSession/events/index.js +31 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/workSession/index.js +48 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/workSession/routes/index.js +15 -0
- package/services/backend/dist/corePlugins/raclette__core/backend/index.js +34 -0
- package/services/backend/dist/domains/index.js +11 -0
- package/services/backend/dist/domains/system/index.js +11 -0
- package/services/backend/dist/domains/system/routes/index.js +17 -0
- package/services/backend/dist/helpers/index.js +14 -0
- package/services/backend/dist/index.js +3 -0
- package/services/backend/dist/modules/authentication/index.js +253 -0
- package/services/backend/dist/shared/types/core/index.js +8 -0
- package/services/backend/dist/shared/types/dataTypes/index.js +6 -0
- package/services/backend/dist/shared/types/index.js +8 -0
- package/services/backend/dist/shared/types/plugins/index.js +8 -0
- package/services/backend/dist/types/index.js +12 -0
- package/services/backend/dist/utils/index.js +2 -0
- package/services/backend/package.json +20 -18
- package/services/backend/src/core/contracting/contractRegistrar.ts +1 -1
- package/services/backend/src/core/pluginSystem/configGenerator/generatorTypes.ts +1 -0
- package/services/backend/src/core/pluginSystem/configGenerator/index.ts +2 -2
- package/services/backend/src/core/pluginSystem/pluginLoader.ts +99 -44
- package/services/backend/src/core/pluginSystem/pluginTypes.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/index.ts +2 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.patchOwn.ts +68 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/user.model.ts +1 -1
- package/services/backend/src/corePlugins/raclette__core/frontend/generated-config.ts +12 -0
- package/services/backend/src/shared/schemas/core/Account.schema.ts +10 -10
- package/services/backend/src/shared/schemas/core/Composition.schema.ts +25 -22
- package/services/backend/src/shared/schemas/core/CompositionCreate.schema.ts +19 -16
- package/services/backend/src/shared/schemas/core/CompositionUpdate.schema.ts +19 -16
- package/services/backend/src/shared/schemas/core/InteractionLink.schema.ts +15 -15
- package/services/backend/src/shared/schemas/core/InteractionLinkCreate.schema.ts +9 -9
- package/services/backend/src/shared/schemas/core/InteractionLinkUpdate.schema.ts +9 -9
- package/services/backend/src/shared/schemas/core/Project.schema.ts +28 -6
- package/services/backend/src/shared/schemas/core/ProjectCreate.schema.ts +22 -0
- package/services/backend/src/shared/schemas/core/ProjectUpdate.schema.ts +25 -0
- package/services/backend/src/shared/schemas/core/Tag.schema.ts +11 -9
- package/services/backend/src/shared/schemas/core/TagCreate.schema.ts +1 -3
- package/services/backend/src/shared/schemas/core/TagUpdate.schema.ts +1 -0
- package/services/backend/src/shared/schemas/core/Trigger.schema.ts +3 -3
- package/services/backend/src/shared/schemas/core/User.schema.ts +18 -18
- package/services/backend/src/shared/schemas/core/UserCreate.schema.ts +9 -9
- package/services/backend/src/shared/schemas/core/Widget.schema.ts +5 -5
- package/services/backend/src/shared/schemas/core/WidgetLayout.schema.ts +9 -9
- package/services/backend/src/shared/types/core/Composition.types.ts +1 -0
- package/services/backend/src/shared/types/core/CompositionCreate.types.ts +1 -0
- package/services/backend/src/shared/types/core/CompositionUpdate.types.ts +1 -0
- package/services/backend/src/shared/types/core/Project.types.ts +4 -0
- package/services/backend/src/shared/types/core/ProjectCreate.types.ts +4 -0
- package/services/backend/src/shared/types/core/ProjectUpdate.types.ts +4 -1
- package/services/backend/src/shared/types/core/Tag.types.ts +1 -1
- package/services/backend/src/shared/types/core/TagCreate.types.ts +0 -1
- package/services/backend/src/shared/types/core/index.ts +4 -0
- package/services/backend/src/shared/types/plugins/index.ts +1 -1
- package/services/backend/tsconfig.json +1 -0
- package/services/backend/yarn.lock +797 -935
- package/services/frontend/.yarn/install-state.gz +0 -0
- package/services/frontend/eslint.config.js +9 -0
- package/services/frontend/index.html +3 -3
- package/services/frontend/package.json +30 -28
- package/services/frontend/src/core/setup/plugin-system/discovery/app-plugins.ts +43 -4
- package/services/frontend/src/orchestrator/ProductOrchestrator.vue +10 -11
- package/services/frontend/src/orchestrator/assets/styles/layers.css +11 -0
- package/services/frontend/src/orchestrator/assets/styles/tailwindStyles.css +5 -11
- package/services/frontend/src/orchestrator/assets/styles/vuetifyStyles.scss +0 -4
- package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +5 -18
- package/services/frontend/src/orchestrator/composables/useWidgets/helperFunctions.ts +54 -5
- package/services/frontend/src/orchestrator/helpers/index.ts +1 -0
- package/services/frontend/src/orchestrator/helpers/widgetVisuals.ts +71 -0
- package/services/frontend/src/orchestrator/setup/vuetify.ts +2 -0
- package/services/frontend/src/orchestrator/types/Widgets.ts +5 -1
- package/services/frontend/vite.config.ts +24 -10
- package/services/frontend/yarn.lock +0 -5224
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getBlockedLogins, resetLoginBlock, resetAllLoginBlocks, } from "./route.admin.cache";
|
|
2
|
+
import deploy from "./route.deploy";
|
|
3
|
+
import login from "./route.user.login";
|
|
4
|
+
import checkToken from "./route.user.checkToken";
|
|
5
|
+
import getAllCollections from "./route.collection.getAll";
|
|
6
|
+
import deleteCollections from "./route.collection.remove";
|
|
7
|
+
const registerRoutes = async (fastify) => {
|
|
8
|
+
await fastify.get("/system/deploy", deploy(fastify));
|
|
9
|
+
await fastify.post("/user/login", login(fastify));
|
|
10
|
+
await fastify.get("/user/check-token", checkToken(fastify));
|
|
11
|
+
await fastify.get("/admin/cache/blocks", getBlockedLogins(fastify));
|
|
12
|
+
await fastify.delete("/admin/cache/blocks/:key", resetLoginBlock(fastify));
|
|
13
|
+
await fastify.delete("/admin/cache/blocks", resetAllLoginBlocks(fastify));
|
|
14
|
+
await fastify.get("/admin/collections/all", getAllCollections(fastify));
|
|
15
|
+
await fastify.delete("/admin/collections", deleteCollections(fastify));
|
|
16
|
+
};
|
|
17
|
+
export default registerRoutes;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { customAlphabet } from "nanoid";
|
|
2
|
+
export * from "./cors";
|
|
3
|
+
const customCyperAlphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
4
|
+
/**
|
|
5
|
+
* Random ID generator
|
|
6
|
+
*
|
|
7
|
+
* @param length - Required length for the output
|
|
8
|
+
* @returns Function that generates random IDs
|
|
9
|
+
* @example
|
|
10
|
+
* const generateId = randomIdGenerator(12);
|
|
11
|
+
* const id = generateId(); // => 'gdh6326GDJh0'
|
|
12
|
+
*/
|
|
13
|
+
const randomIdGenerator = (length = 16) => customAlphabet(customCyperAlphabet, length);
|
|
14
|
+
export { randomIdGenerator };
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import jwt from "@fastify/jwt";
|
|
2
|
+
import fastifyPlugin from "fastify-plugin";
|
|
3
|
+
import configService from "@c/config/configService";
|
|
4
|
+
import { ForbiddenError } from "@/utils/errors";
|
|
5
|
+
const WORK_SESSION_COLLECTION = "raclette__core-worksession";
|
|
6
|
+
const WORK_SESSION_HEADER = "x-work-session-id";
|
|
7
|
+
/**
|
|
8
|
+
* Middleware: allow either authenticated user (JWT) or valid work session (X-Work-Session-Id).
|
|
9
|
+
* When using this, the route states that it handles permission checks itself (e.g. compare req.workSession with query).
|
|
10
|
+
* - If JWT present and valid: sets req.user (normal auth).
|
|
11
|
+
* - If no/invalid JWT but X-Work-Session-Id present: loads work session, validates (active, not expired),
|
|
12
|
+
* optionally checks that workSession.pluginData includes the given pluginKey, then sets req.workSession and req.isWorkSession.
|
|
13
|
+
*/
|
|
14
|
+
export const authenticatedOrWorksession = async (req, res, fastify, pluginKey) => {
|
|
15
|
+
fastify.log.debug(`[Auth] authenticatedOrWorksession: ${pluginKey} ${req.method} ${req.url}`);
|
|
16
|
+
const { requireAuthentication } = configService.getConfig()?.global ?? {
|
|
17
|
+
requireAuthentication: true,
|
|
18
|
+
};
|
|
19
|
+
const isAdminTagRequired = configService.isAdminTagRequired();
|
|
20
|
+
if (!requireAuthentication) {
|
|
21
|
+
const mockUser = { _id: "SYSTEM" };
|
|
22
|
+
req.user = mockUser;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// 1) Try JWT first
|
|
26
|
+
try {
|
|
27
|
+
await req.jwtVerify();
|
|
28
|
+
const payload = req.user;
|
|
29
|
+
fastify.log.debug(`[Auth] JWT verified, payload type: ${payload.type || "user"}, sessionId: ${payload.sessionId || "none"}, _id: ${payload._id || "none"}`);
|
|
30
|
+
if (payload.type === "workSession" && payload.sessionId) {
|
|
31
|
+
fastify.log.info(`[Auth] Work session JWT detected, sessionId: ${payload.sessionId}, pluginKey: ${pluginKey}`);
|
|
32
|
+
// Work session JWT: load session and validate
|
|
33
|
+
const workSession = await loadAndValidateWorkSession(fastify, payload.sessionId, pluginKey);
|
|
34
|
+
if (!workSession) {
|
|
35
|
+
fastify.log.warn(`[Auth] Work session validation failed for sessionId: ${payload.sessionId}`);
|
|
36
|
+
return res
|
|
37
|
+
.status(401)
|
|
38
|
+
.send({ message: "Invalid or expired work session" });
|
|
39
|
+
}
|
|
40
|
+
fastify.log.info(`[Auth] Work session authenticated: sessionId=${payload.sessionId}, pluginKey=${pluginKey}`);
|
|
41
|
+
req.workSession = workSession;
|
|
42
|
+
req.isWorkSession = true;
|
|
43
|
+
// Set projectId from workSession.project if available
|
|
44
|
+
if (workSession.project) {
|
|
45
|
+
req.projectId = workSession.project;
|
|
46
|
+
fastify.log.debug(`[Auth] Work session projectId: ${req.projectId}`);
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Normal user JWT: fetch user and set req.user
|
|
51
|
+
fastify.log.debug(`[Auth] Normal user JWT, fetching user: ${req.user?._id}`);
|
|
52
|
+
const { _id } = req.user;
|
|
53
|
+
const adminTag = await fastify.database
|
|
54
|
+
.collection("raclette__core-tag")
|
|
55
|
+
.findOne({ title: "ADMIN", locked: true, type: "system" });
|
|
56
|
+
let isAdmin = false;
|
|
57
|
+
const user = await fastify.database
|
|
58
|
+
.collection("raclette__core-user")
|
|
59
|
+
.findOne({ _id }, { projection: { password: 0, resetKey: 0 } });
|
|
60
|
+
if (user && user.tags && adminTag) {
|
|
61
|
+
user.tags.forEach((tag) => {
|
|
62
|
+
if (adminTag._id === tag)
|
|
63
|
+
isAdmin = true;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (isAdminTagRequired && !isAdmin) {
|
|
67
|
+
fastify.log.error("User not allowed in workbench");
|
|
68
|
+
return res.status(403).send({ message: "User not allowed in workbench" });
|
|
69
|
+
}
|
|
70
|
+
if (isAdmin)
|
|
71
|
+
user.isAdmin = true;
|
|
72
|
+
if (!user) {
|
|
73
|
+
fastify.log.error("User not found");
|
|
74
|
+
return res.status(401).send({ message: "User not found" });
|
|
75
|
+
}
|
|
76
|
+
const account = await fastify.database
|
|
77
|
+
.collection("raclette__core-account")
|
|
78
|
+
.findOne({ _id: user.account });
|
|
79
|
+
user.account = account;
|
|
80
|
+
req.user = user;
|
|
81
|
+
// Set projectId from user's lastProjectId (for requestData.project in payload wrappers)
|
|
82
|
+
if (user.ux?.lastProjectId) {
|
|
83
|
+
req.projectId = user.ux.lastProjectId;
|
|
84
|
+
fastify.log.debug(`[Auth] User projectId: ${req.projectId}`);
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
// JWT missing or invalid: try work session via header
|
|
90
|
+
fastify.log.debug(`[Auth] JWT verification failed: ${error.message}, trying work session header`);
|
|
91
|
+
}
|
|
92
|
+
// 2) Try X-Work-Session-Id header
|
|
93
|
+
const sessionId = req.headers[WORK_SESSION_HEADER];
|
|
94
|
+
if (sessionId) {
|
|
95
|
+
const workSession = await loadAndValidateWorkSession(fastify, sessionId, pluginKey);
|
|
96
|
+
if (workSession) {
|
|
97
|
+
req.workSession = workSession;
|
|
98
|
+
req.isWorkSession = true;
|
|
99
|
+
// Set projectId from workSession.project if available
|
|
100
|
+
// workaround for how projects/spaces are currently handled - tbd
|
|
101
|
+
if (workSession.project) {
|
|
102
|
+
req.projectId = workSession.project;
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
fastify.log.warn(`[Auth] Work session validation failed for header sessionId: ${sessionId}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return res.status(401).send({ message: "Authentication required" });
|
|
111
|
+
};
|
|
112
|
+
async function loadAndValidateWorkSession(fastify, sessionId, pluginKey) {
|
|
113
|
+
fastify.log.debug(`[WorkSession Auth] Loading and validating work session: sessionId=${sessionId}, pluginKey=${pluginKey}`);
|
|
114
|
+
if (!sessionId || typeof sessionId !== "string") {
|
|
115
|
+
fastify.log.warn(`[WorkSession Auth] Invalid sessionId: ${sessionId}`);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
fastify.log.debug(`[WorkSession Auth] Querying database for work session`);
|
|
119
|
+
const doc = await fastify.database
|
|
120
|
+
.collection(WORK_SESSION_COLLECTION)
|
|
121
|
+
.findOne({ _id: sessionId });
|
|
122
|
+
if (!doc) {
|
|
123
|
+
fastify.log.warn(`[WorkSession Auth] Work session not found: ${sessionId}`);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
if (!doc.isActive) {
|
|
127
|
+
fastify.log.warn(`[WorkSession Auth] Work session is not active: ${sessionId}`);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const expiry = doc.expiryDate ? new Date(doc.expiryDate) : null;
|
|
131
|
+
if (expiry && expiry.getTime() < Date.now()) {
|
|
132
|
+
fastify.log.warn(`[WorkSession Auth] Work session expired: ${sessionId}, expiryDate: ${expiry.toISOString()}`);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
// Check that this plugin is part of the work session (pluginData keys are "pluginKey:widgetName")
|
|
136
|
+
const pluginData = doc.pluginData || {};
|
|
137
|
+
const pluginKeys = Object.keys(pluginData);
|
|
138
|
+
const hasPlugin = pluginKeys.some((k) => k.startsWith(pluginKey + ":")) ||
|
|
139
|
+
Object.prototype.hasOwnProperty.call(pluginData, pluginKey);
|
|
140
|
+
if (!hasPlugin) {
|
|
141
|
+
fastify.log.warn(`[WorkSession Auth] Plugin ${pluginKey} not found in work session pluginData: ${pluginKeys.join(", ")}`);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
fastify.log.info(`[WorkSession Auth] Work session validated successfully: sessionId=${sessionId}, pluginKey=${pluginKey}`);
|
|
145
|
+
return doc;
|
|
146
|
+
}
|
|
147
|
+
export const authenticate = async (req, res, fastify) => {
|
|
148
|
+
{
|
|
149
|
+
const { requireAuthentication } = configService.getConfig()?.global ?? {
|
|
150
|
+
requireAuthentication: true,
|
|
151
|
+
};
|
|
152
|
+
const isAdminTagRequired = configService.isAdminTagRequired();
|
|
153
|
+
// Skip JWT verification and database queries, if global.requireAuthentication is configured to false
|
|
154
|
+
if (!requireAuthentication) {
|
|
155
|
+
const mockUser = {
|
|
156
|
+
_id: "SYSTEM",
|
|
157
|
+
};
|
|
158
|
+
req.user = mockUser;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
await req.jwtVerify();
|
|
163
|
+
const payload = req.user;
|
|
164
|
+
if (payload.type === "workSession") {
|
|
165
|
+
return res
|
|
166
|
+
.status(401)
|
|
167
|
+
.send({ message: "Work session token not allowed on this route" });
|
|
168
|
+
}
|
|
169
|
+
// fetch current user data from the db
|
|
170
|
+
const { _id } = req.user;
|
|
171
|
+
const adminTag = await fastify.database
|
|
172
|
+
.collection("raclette__core-tag")
|
|
173
|
+
.findOne({ title: "ADMIN", locked: true, type: "system" });
|
|
174
|
+
// IMPORTANT
|
|
175
|
+
// due to some very weird behaviour, mongoose always returns empty results
|
|
176
|
+
// so we are using mongodb directly until we fix this
|
|
177
|
+
let isAdmin = false;
|
|
178
|
+
const user = await fastify.database
|
|
179
|
+
.collection("raclette__core-user")
|
|
180
|
+
.findOne({ _id }, { projection: { password: 0, resetKey: 0 } });
|
|
181
|
+
if (user && user.tags && adminTag) {
|
|
182
|
+
user.tags.forEach((tag) => {
|
|
183
|
+
if (adminTag._id === tag) {
|
|
184
|
+
isAdmin = true;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (isAdminTagRequired && !isAdmin) {
|
|
189
|
+
fastify.log.error("User not allowed in workbench");
|
|
190
|
+
throw new Error("User not allowed in workbench");
|
|
191
|
+
}
|
|
192
|
+
if (isAdmin) {
|
|
193
|
+
user.isAdmin = true;
|
|
194
|
+
}
|
|
195
|
+
if (!user) {
|
|
196
|
+
fastify.log.error("User not found");
|
|
197
|
+
throw new Error("User not found");
|
|
198
|
+
}
|
|
199
|
+
const account = await fastify.database
|
|
200
|
+
.collection("raclette__core-account")
|
|
201
|
+
.findOne({ _id: user.account });
|
|
202
|
+
user.account = account;
|
|
203
|
+
req.user = user;
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
return res.send(error);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
export const checkPermissions = async (req, res, fastify, permissions) => {
|
|
211
|
+
{
|
|
212
|
+
try {
|
|
213
|
+
if (!permissions.length) {
|
|
214
|
+
fastify.log.error("No permissions given for permission check");
|
|
215
|
+
throw new Error("No Permissions");
|
|
216
|
+
}
|
|
217
|
+
// TODO implement proper Permission lookup here
|
|
218
|
+
permissions.forEach((permissionStr) => {
|
|
219
|
+
if (permissionStr === "user.isAdmin") {
|
|
220
|
+
if (!req.user.isAdmin) {
|
|
221
|
+
throw new ForbiddenError("User not permitted to action");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
fastify.log.error(error.message);
|
|
228
|
+
return res.send(error.message);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
export default fastifyPlugin(async (fastify, opts = {}) => {
|
|
233
|
+
// provide some sensible options
|
|
234
|
+
const defaults = {
|
|
235
|
+
secret: false,
|
|
236
|
+
decode: { complete: true },
|
|
237
|
+
sign: {
|
|
238
|
+
iss: "api.raclettejs.info",
|
|
239
|
+
expiresIn: "30d",
|
|
240
|
+
},
|
|
241
|
+
verify: { allowedIss: "api.raclettejs.info" },
|
|
242
|
+
};
|
|
243
|
+
// merge proved opts with defaults
|
|
244
|
+
const mergedOptions = { ...defaults, ...opts };
|
|
245
|
+
/* register the plugin */
|
|
246
|
+
fastify.register(jwt, mergedOptions);
|
|
247
|
+
// decorate the fastify instance, so we can call it anywhere
|
|
248
|
+
fastify.decorate("authenticate", (req, res) => {
|
|
249
|
+
return authenticate(req, res, fastify);
|
|
250
|
+
});
|
|
251
|
+
fastify.decorate("authenticatedOrWorksession", (pluginKey) => (req, res) => authenticatedOrWorksession(req, res, fastify, pluginKey));
|
|
252
|
+
fastify.decorate("checkPermissions", (permissionsArr) => (req, res) => checkPermissions(req, res, fastify, permissionsArr));
|
|
253
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from "./tags";
|
|
2
|
+
// TODO make the mongoDataTypes dynamic
|
|
3
|
+
export const systemDataTypes = ["account", "projectConfig", "userConfig"];
|
|
4
|
+
export const userContents = ["tag", "user", "project"];
|
|
5
|
+
export const mongoDataTypes = systemDataTypes.concat(userContents);
|
|
6
|
+
export const syncableDatatypes = ["tag", "user", "searchResult"];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "../core/backgroundTasks/taskManagerTypes";
|
|
2
|
+
export * from "../core/config/configTypes";
|
|
3
|
+
export * from "../core/crud/crudTypes";
|
|
4
|
+
export * from "../core/http/httpTypes";
|
|
5
|
+
export * from "../core/http/httpTypes";
|
|
6
|
+
export * from "../core/payload/payloadTypes";
|
|
7
|
+
export * from "../core/pluginSystem/pluginTypes";
|
|
8
|
+
export * from "../core/sockets/socketTypes";
|
|
9
|
+
export * from "../core/validation/schemaTypes";
|
|
10
|
+
export * from "../modules/cache/cacheTypes";
|
|
11
|
+
export * from "../core/validation/schemaTypes";
|
|
12
|
+
export * from "../core/pluginSystem/configGenerator/generatorTypes";
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"build:assets:src": "copyfiles -u 1 'src/**/*.{json,js,yml,yaml,env*}' dist",
|
|
17
17
|
"build:assets:root": "copyfiles -u 0 'raclette.config.js' dist",
|
|
18
18
|
"build:assets": "npm run build:assets:src && npm run build:assets:root",
|
|
19
|
+
"lint": "eslint .",
|
|
19
20
|
"migrate:create": "npx migrate-mongo create -f './mongo-migrations/migrate-mongo-config.js'",
|
|
20
21
|
"migrate:up": "migrate-mongo up -f './mongo-migrations/migrate-mongo-config.js'",
|
|
21
22
|
"migrate:down": "migrate-mongo down -f './mongo-migrations/migrate-mongo-config.js'",
|
|
@@ -24,28 +25,29 @@
|
|
|
24
25
|
"typecheck:watch": "tsc --noEmit --watch"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
27
|
-
"@fastify/cors": "11.
|
|
28
|
+
"@fastify/cors": "11.2.0",
|
|
28
29
|
"@fastify/jwt": "10.0.0",
|
|
29
30
|
"@fastify/rate-limit": "10.3.0",
|
|
30
31
|
"@fastify/schedule": "6.0.0",
|
|
31
|
-
"@fastify/sensible": "6.0.
|
|
32
|
-
"@sinclair/typebox": "0.34.
|
|
32
|
+
"@fastify/sensible": "6.0.4",
|
|
33
|
+
"@sinclair/typebox": "0.34.48",
|
|
33
34
|
"@types/vary": "1.1.3",
|
|
34
|
-
"axios": "1.13.
|
|
35
|
+
"axios": "1.13.6",
|
|
35
36
|
"bcrypt": "6.0.0",
|
|
36
37
|
"copyfiles": "2.4.1",
|
|
37
|
-
"fastify": "5.
|
|
38
|
+
"fastify": "5.8.2",
|
|
38
39
|
"fastify-custom-healthcheck": "4.0.0",
|
|
39
|
-
"
|
|
40
|
+
"fs-extra": "11.3.4",
|
|
41
|
+
"glob": "13.0.6",
|
|
40
42
|
"iovalkey": "0.3.3",
|
|
41
|
-
"migrate-mongo": "
|
|
42
|
-
"mongodb": "
|
|
43
|
-
"mongoose": "8.18.
|
|
44
|
-
"nanoid": "5.1.
|
|
45
|
-
"pino": "
|
|
46
|
-
"pino-pretty": "13.1.
|
|
47
|
-
"ramda": "0.
|
|
48
|
-
"socket.io": "4.8.
|
|
43
|
+
"migrate-mongo": "14.0.7",
|
|
44
|
+
"mongodb": "6.21.0",
|
|
45
|
+
"mongoose": "8.18.3",
|
|
46
|
+
"nanoid": "5.1.7",
|
|
47
|
+
"pino": "10.3.1",
|
|
48
|
+
"pino-pretty": "13.1.3",
|
|
49
|
+
"ramda": "0.32.0",
|
|
50
|
+
"socket.io": "4.8.3",
|
|
49
51
|
"toad-scheduler": "3.1.0",
|
|
50
52
|
"tsc-alias": "1.8.16",
|
|
51
53
|
"uuid": "13.0.0"
|
|
@@ -58,9 +60,9 @@
|
|
|
58
60
|
"@types/uuid": "11.0.0",
|
|
59
61
|
"eslint": "9.35.0",
|
|
60
62
|
"eslint-plugin-import": "2.32.0",
|
|
61
|
-
"eslint-plugin-prefer-arrow-functions": "3.
|
|
62
|
-
"tsx": "4.
|
|
63
|
-
"typescript": "5.9.
|
|
64
|
-
"typescript-eslint": "8.
|
|
63
|
+
"eslint-plugin-prefer-arrow-functions": "3.9.1",
|
|
64
|
+
"tsx": "4.21.0",
|
|
65
|
+
"typescript": "5.9.3",
|
|
66
|
+
"typescript-eslint": "8.57.1"
|
|
65
67
|
}
|
|
66
68
|
}
|
|
@@ -12,7 +12,7 @@ export const registerPluginContract = (
|
|
|
12
12
|
contract: PluginContract,
|
|
13
13
|
): void => {
|
|
14
14
|
if (contracts[pluginKey]) {
|
|
15
|
-
throw new Error(`Plugin contract for "${pluginKey}" is already registered
|
|
15
|
+
throw new Error(`Plugin contract for "${pluginKey}" is already registered. Make sure that each plugin is calling fastify.generatePluginContract only once! Multiple calls are not allowed.`)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
contracts[pluginKey] = contract
|
|
@@ -42,6 +42,7 @@ class FrontendConfigRegistry {
|
|
|
42
42
|
author: pluginData.author,
|
|
43
43
|
pluginKey: pluginData.key,
|
|
44
44
|
pluginPath: pluginData.absolutePath,
|
|
45
|
+
frontendPath: pluginData.frontendAbsolutePath,
|
|
45
46
|
routePrefix: pluginData.routePrefix,
|
|
46
47
|
entityMapping: options?.entityMapping || {},
|
|
47
48
|
customConfig: options?.customConfig,
|
|
@@ -451,8 +452,7 @@ const writePluginConfigFile = async (
|
|
|
451
452
|
|
|
452
453
|
const configContent = generatePluginConfigFile(dataTypes, registration)
|
|
453
454
|
const outputPath = path.join(
|
|
454
|
-
registration.
|
|
455
|
-
"frontend",
|
|
455
|
+
registration.frontendPath,
|
|
456
456
|
"generated-config.ts",
|
|
457
457
|
)
|
|
458
458
|
|
|
@@ -30,6 +30,7 @@ interface PluginInfo {
|
|
|
30
30
|
pluginName: string
|
|
31
31
|
modulePath: string
|
|
32
32
|
pluginPath: string
|
|
33
|
+
frontendPath: string
|
|
33
34
|
type: PluginType
|
|
34
35
|
metadata: PluginMetadata
|
|
35
36
|
}
|
|
@@ -75,6 +76,74 @@ const logPluginError = (
|
|
|
75
76
|
fastify.log.debug(`${createLogPrefix(type)} ${identifier}: ERROR - ${error}`)
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
const normalizeRelativeDir = (dir?: string): string | undefined =>
|
|
80
|
+
dir?.replace(/^\.\//, "")
|
|
81
|
+
|
|
82
|
+
const resolveExistingServicePath = async (
|
|
83
|
+
pluginPath: string,
|
|
84
|
+
candidates: string[],
|
|
85
|
+
): Promise<{ relativePath: string; absolutePath: string } | undefined> => {
|
|
86
|
+
for (const candidatePath of candidates) {
|
|
87
|
+
const normalizedCandidatePath = normalizeRelativeDir(candidatePath)
|
|
88
|
+
if (!normalizedCandidatePath) continue
|
|
89
|
+
|
|
90
|
+
const candidateAbsolutePath = path.join(pluginPath, normalizedCandidatePath)
|
|
91
|
+
try {
|
|
92
|
+
const stat = await fs.promises.stat(candidateAbsolutePath)
|
|
93
|
+
if (stat.isDirectory()) {
|
|
94
|
+
return {
|
|
95
|
+
relativePath: normalizedCandidatePath,
|
|
96
|
+
absolutePath: candidateAbsolutePath,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// path missing or not accessible
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const resolveBackendPath = async (
|
|
106
|
+
pluginPath: string,
|
|
107
|
+
metadata: PluginMetadata,
|
|
108
|
+
): Promise<{ relativePath: string; absolutePath: string } | undefined> => {
|
|
109
|
+
const backendPathCandidates = getBackendPathCandidates(metadata)
|
|
110
|
+
|
|
111
|
+
return resolveExistingServicePath(pluginPath, backendPathCandidates)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const getBackendPathCandidates = (metadata: PluginMetadata): string[] => {
|
|
115
|
+
const explicitBackendDir = normalizeRelativeDir(metadata.backendDir)
|
|
116
|
+
if (explicitBackendDir) {
|
|
117
|
+
return [explicitBackendDir]
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return ["backend", "src/backend"]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const resolveFrontendPath = async (
|
|
124
|
+
pluginPath: string,
|
|
125
|
+
metadata: PluginMetadata,
|
|
126
|
+
backendRelativePath?: string,
|
|
127
|
+
): Promise<string> => {
|
|
128
|
+
const explicitFrontendDir = normalizeRelativeDir(metadata.frontendDir)
|
|
129
|
+
if (explicitFrontendDir) {
|
|
130
|
+
return path.join(pluginPath, explicitFrontendDir)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const resolvedFrontendPath = await resolveExistingServicePath(pluginPath, [
|
|
134
|
+
"frontend",
|
|
135
|
+
"src/frontend",
|
|
136
|
+
])
|
|
137
|
+
if (resolvedFrontendPath) {
|
|
138
|
+
return resolvedFrontendPath.absolutePath
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Fall back to src/frontend when backend is under src, otherwise frontend.
|
|
142
|
+
const fallbackRelativePath =
|
|
143
|
+
backendRelativePath?.startsWith("src/") ? "src/frontend" : "frontend"
|
|
144
|
+
return path.join(pluginPath, fallbackRelativePath)
|
|
145
|
+
}
|
|
146
|
+
|
|
78
147
|
/**
|
|
79
148
|
* Get app plugin paths for current environment
|
|
80
149
|
*/
|
|
@@ -254,16 +323,18 @@ const collectLocalPluginsFromPath = async (
|
|
|
254
323
|
continue
|
|
255
324
|
}
|
|
256
325
|
|
|
257
|
-
// Get backend path from metadata or
|
|
258
|
-
const
|
|
259
|
-
const
|
|
326
|
+
// Get backend path from metadata or try supported defaults.
|
|
327
|
+
const resolvedBackendPath = await resolveBackendPath(pluginPath, metadata)
|
|
328
|
+
const backendRelativePath = resolvedBackendPath?.relativePath
|
|
329
|
+
const backendPath = resolvedBackendPath?.absolutePath
|
|
260
330
|
|
|
261
|
-
if (!
|
|
331
|
+
if (!backendRelativePath || !backendPath) {
|
|
332
|
+
const backendPathCandidates = getBackendPathCandidates(metadata)
|
|
262
333
|
logPluginSkipped(
|
|
263
334
|
fastify,
|
|
264
335
|
pluginType,
|
|
265
336
|
pluginIdentifier,
|
|
266
|
-
`backend directory not found: ${
|
|
337
|
+
`backend directory not found: ${backendPathCandidates.join(" or ")}`,
|
|
267
338
|
)
|
|
268
339
|
if (metadata.backendDir) {
|
|
269
340
|
// we only cancel when the dir was explicitly set
|
|
@@ -277,24 +348,6 @@ const collectLocalPluginsFromPath = async (
|
|
|
277
348
|
continue
|
|
278
349
|
}
|
|
279
350
|
|
|
280
|
-
if (!fs.statSync(backendPath).isDirectory()) {
|
|
281
|
-
logPluginSkipped(
|
|
282
|
-
fastify,
|
|
283
|
-
pluginType,
|
|
284
|
-
pluginIdentifier,
|
|
285
|
-
`backend path is not a directory: ${backendRelativePath}`,
|
|
286
|
-
)
|
|
287
|
-
if (metadata.backendDir) {
|
|
288
|
-
handlePluginError(
|
|
289
|
-
fastify,
|
|
290
|
-
`Server directory not found for plugin: ${configPath}`,
|
|
291
|
-
pluginType,
|
|
292
|
-
errorFlags,
|
|
293
|
-
)
|
|
294
|
-
}
|
|
295
|
-
continue
|
|
296
|
-
}
|
|
297
|
-
|
|
298
351
|
// Find the main module file
|
|
299
352
|
const modulePath = getFilePathWithExtension(backendPath, "index")
|
|
300
353
|
if (!modulePath) {
|
|
@@ -315,12 +368,18 @@ const collectLocalPluginsFromPath = async (
|
|
|
315
368
|
|
|
316
369
|
const author = slugify(metadata.author)
|
|
317
370
|
const pluginName = slugify(metadata.name)
|
|
371
|
+
const frontendPath = await resolveFrontendPath(
|
|
372
|
+
pluginPath,
|
|
373
|
+
metadata,
|
|
374
|
+
backendRelativePath,
|
|
375
|
+
)
|
|
318
376
|
|
|
319
377
|
plugins.push({
|
|
320
378
|
author,
|
|
321
379
|
pluginName,
|
|
322
380
|
modulePath,
|
|
323
381
|
pluginPath,
|
|
382
|
+
frontendPath,
|
|
324
383
|
type: pluginType,
|
|
325
384
|
metadata,
|
|
326
385
|
})
|
|
@@ -462,32 +521,21 @@ const collectNpmPlugins = async (
|
|
|
462
521
|
continue
|
|
463
522
|
}
|
|
464
523
|
|
|
465
|
-
// Get backend path from metadata or
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
PluginType.NPM,
|
|
473
|
-
pluginPackage,
|
|
474
|
-
`backend directory not found: ${backendRelativePath}`,
|
|
475
|
-
)
|
|
476
|
-
handlePluginError(
|
|
477
|
-
fastify,
|
|
478
|
-
`Server directory not found for npm plugin: ${pluginPackage}`,
|
|
479
|
-
PluginType.NPM,
|
|
480
|
-
errorFlags,
|
|
481
|
-
)
|
|
482
|
-
continue
|
|
483
|
-
}
|
|
524
|
+
// Get backend path from metadata or try supported defaults.
|
|
525
|
+
const resolvedBackendPath = await resolveBackendPath(
|
|
526
|
+
pluginPath,
|
|
527
|
+
pluginMetadata,
|
|
528
|
+
)
|
|
529
|
+
const backendRelativePath = resolvedBackendPath?.relativePath
|
|
530
|
+
const backendPath = resolvedBackendPath?.absolutePath
|
|
484
531
|
|
|
485
|
-
if (!
|
|
532
|
+
if (!backendRelativePath || !backendPath) {
|
|
533
|
+
const backendPathCandidates = getBackendPathCandidates(pluginMetadata)
|
|
486
534
|
logPluginSkipped(
|
|
487
535
|
fastify,
|
|
488
536
|
PluginType.NPM,
|
|
489
537
|
pluginPackage,
|
|
490
|
-
`backend
|
|
538
|
+
`backend directory not found: ${backendPathCandidates.join(" or ")}`,
|
|
491
539
|
)
|
|
492
540
|
handlePluginError(
|
|
493
541
|
fastify,
|
|
@@ -518,12 +566,18 @@ const collectNpmPlugins = async (
|
|
|
518
566
|
|
|
519
567
|
const author = slugify(pluginMetadata.author)
|
|
520
568
|
const pluginName = slugify(pluginMetadata.name)
|
|
569
|
+
const frontendPath = await resolveFrontendPath(
|
|
570
|
+
pluginPath,
|
|
571
|
+
pluginMetadata,
|
|
572
|
+
backendRelativePath,
|
|
573
|
+
)
|
|
521
574
|
|
|
522
575
|
plugins.push({
|
|
523
576
|
author,
|
|
524
577
|
pluginName,
|
|
525
578
|
modulePath,
|
|
526
579
|
pluginPath,
|
|
580
|
+
frontendPath,
|
|
527
581
|
type: PluginType.NPM,
|
|
528
582
|
metadata: pluginMetadata,
|
|
529
583
|
})
|
|
@@ -704,6 +758,7 @@ const registerCollectedPlugins = async (
|
|
|
704
758
|
collectionPrefix,
|
|
705
759
|
routePrefix,
|
|
706
760
|
absolutePath: plugin.pluginPath,
|
|
761
|
+
frontendAbsolutePath: plugin.frontendPath,
|
|
707
762
|
}
|
|
708
763
|
|
|
709
764
|
// Dynamically import the plugin module
|