@raclettejs/core 0.1.18 → 0.1.20
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 +23 -0
- package/bin/cli.js +1 -1
- package/dev/README.md +4 -0
- package/dist/cli.js +135 -124
- package/dist/cli.js.map +4 -4
- package/dist/index.js +9 -8
- package/dist/index.js.map +4 -4
- package/package.json +9 -5
- package/services/backend/delay-api-debugger.sh +0 -0
- package/services/backend/package.json +1 -0
- package/services/backend/src/app.ts +4 -0
- package/services/backend/src/core/config/configService.ts +10 -1
- package/services/backend/src/core/events/eventEmitter.ts +32 -7
- package/services/backend/src/core/http/request-params.plugin.ts +2 -2
- package/services/backend/src/core/pluginSystem/pluginFastifyInstance.ts +4 -1
- package/services/backend/src/core/pluginSystem/pluginTypes.ts +3 -1
- package/services/backend/src/core/sockets/createSystemEventHandlers.ts +7 -0
- package/services/backend/src/core/sockets/index.ts +61 -13
- package/services/backend/src/core/sockets/socketManager.ts +452 -105
- package/services/backend/src/core/sockets/socketTypes.ts +13 -1
- package/services/backend/src/core/sockets/workSessionJoinPayload.ts +106 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.get.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.getAll.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.hardDelete.ts +16 -19
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.patch.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.post.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.remove.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/composition.service.ts +86 -3
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/index.ts +2 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.get.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.getAll.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.hardDelete.ts +16 -19
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.patch.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.post.bulk.ts +60 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.post.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.remove.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.restore.ts +12 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/interactionLink.service.ts +96 -12
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/index.ts +2 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.get.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.getAll.ts +4 -2
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.hardDelete.ts +12 -17
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.patch.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.post.bulk.ts +62 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.post.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.remove.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.restore.ts +12 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/project.service.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.get.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.getAll.ts +4 -2
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.hardDelete.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.patch.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.post.ts +4 -2
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.remove.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/index.ts +2 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.get.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.getAll.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.hardDelete.ts +10 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.patch.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.post.bulk.ts +60 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.post.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.remove.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.restore.ts +12 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/tag.service.ts +73 -3
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.get.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.getAll.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.hardDelete.ts +10 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.patch.ts +4 -6
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.patchLastProject.ts +4 -2
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.post.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.remove.ts +3 -4
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.restore.ts +12 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/events/index.ts +45 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/helpers/crud.ts +12 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/helpers/payload.ts +21 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/index.ts +55 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/index.ts +19 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.get.ts +53 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.getAll.ts +39 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.hardDelete.ts +53 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.patch.ts +58 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.post.ts +53 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.remove.ts +52 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/workSession.model.ts +65 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/workSession.schema.ts +125 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/workSession.service.ts +351 -0
- package/services/backend/src/corePlugins/raclette__core/backend/index.ts +2 -0
- package/services/backend/src/corePlugins/raclette__core/frontend/generated-config.ts +159 -24
- package/services/backend/src/modules/authentication/index.ts +255 -4
- package/services/backend/src/modules/hooks/rate-limiter.ts +21 -0
- package/services/backend/src/types/custom-fastify.d.ts +17 -0
- package/services/backend/src/utils/request.utils.ts +4 -1
- package/services/backend/types/index.d.ts +15 -1
- package/services/backend/yarn.lock +13 -4
- package/services/frontend/index.html +1 -1
- package/services/frontend/provision/entrypoint.sh +0 -0
- package/services/frontend/src/core/lib/configService.ts +1 -1
- package/services/frontend/src/core/lib/data/dataApi.ts +10 -1
- package/services/frontend/src/core/lib/dataHelper.ts +1 -1
- package/services/frontend/src/core/lib/eggs/snow.ts +66 -48
- package/services/frontend/src/core/lib/helpers/index.ts +1 -0
- package/services/frontend/src/core/lib/helpers/removeEmpty.ts +69 -0
- package/services/frontend/src/core/lib/httpClient.ts +19 -0
- package/services/frontend/src/core/main.ts +20 -10
- package/services/frontend/src/core/setup/socket.ts +4 -3
- package/services/frontend/src/core/setup/socketClient.ts +31 -3
- package/services/frontend/src/core/setup/socketEvents.ts +92 -8
- package/services/frontend/src/core/store/index.ts +5 -1
- package/services/frontend/src/core/store/reducers/compositions/index.ts +2 -2
- package/services/frontend/src/core/store/reducers/interactionLinks/index.ts +1 -1
- package/services/frontend/src/core/store/reducers/widgets/reducers.ts +1 -2
- package/services/frontend/src/core/store/types/index.ts +0 -1
- package/services/frontend/src/core/types/PluginRegistrar.ts +8 -6
- package/services/frontend/src/core/types/Store.ts +1 -0
- package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +68 -8
- package/services/frontend/src/orchestrator/components/menu/UserMenu.vue +17 -5
- package/services/frontend/src/orchestrator/components/welcomeScreen/AdminWelcomeScreen.vue +1 -1
- package/services/frontend/src/orchestrator/composables/index.ts +1 -0
- package/services/frontend/src/orchestrator/composables/usePluginApi.ts +0 -3
- package/services/frontend/src/orchestrator/composables/useRouteState.ts +23 -4
- package/services/frontend/src/orchestrator/composables/useWidgetLifecycle.ts +166 -0
- package/services/frontend/src/orchestrator/helpers/index.ts +1 -0
- package/services/frontend/src/orchestrator/i18n/de-DE.json +15 -1
- package/services/frontend/src/orchestrator/i18n/en-EU.json +15 -1
- package/services/frontend/src/orchestrator/i18n/sk.json +15 -1
- package/services/frontend/src/orchestrator/index.ts +34 -4
- package/services/frontend/src/orchestrator/router/routeParserHelper.ts +106 -1
- package/services/frontend/src/orchestrator/types/Widgets.ts +2 -0
- package/services/frontend/types.ts +1 -0
- package/src/README.md +404 -0
- package/src/types.ts +19 -5
- package/templates/README.md +0 -0
- package/types/index.ts +18 -4
- package/services/backend/.gitignore +0 -17
- package/services/frontend/.gitignore +0 -35
- package/yarn.lock +0 -2937
|
@@ -3,6 +3,214 @@ import jwt from "@fastify/jwt"
|
|
|
3
3
|
import fastifyPlugin from "fastify-plugin"
|
|
4
4
|
import type { User } from "@shared/types"
|
|
5
5
|
import configService from "@c/config/configService"
|
|
6
|
+
import type { WorkSessionDoc } from "@/corePlugins/raclette__core/backend/datatypes/workSession/workSession.schema"
|
|
7
|
+
import { ForbiddenError } from "@/utils/errors"
|
|
8
|
+
|
|
9
|
+
const WORK_SESSION_COLLECTION = "raclette__core-worksession"
|
|
10
|
+
const WORK_SESSION_HEADER = "x-work-session-id"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Middleware: allow either authenticated user (JWT) or valid work session (X-Work-Session-Id).
|
|
14
|
+
* When using this, the route states that it handles permission checks itself (e.g. compare req.workSession with query).
|
|
15
|
+
* - If JWT present and valid: sets req.user (normal auth).
|
|
16
|
+
* - If no/invalid JWT but X-Work-Session-Id present: loads work session, validates (active, not expired),
|
|
17
|
+
* optionally checks that workSession.pluginData includes the given pluginKey, then sets req.workSession and req.isWorkSession.
|
|
18
|
+
*/
|
|
19
|
+
export const authenticatedOrWorksession = async (
|
|
20
|
+
req: FastifyRequest,
|
|
21
|
+
res: FastifyReply,
|
|
22
|
+
fastify: FastifyInstance,
|
|
23
|
+
pluginKey: string,
|
|
24
|
+
) => {
|
|
25
|
+
fastify.log.debug(
|
|
26
|
+
`[Auth] authenticatedOrWorksession: ${pluginKey} ${req.method} ${req.url}`,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const { requireAuthentication } = configService.getConfig()?.global ?? {
|
|
30
|
+
requireAuthentication: true,
|
|
31
|
+
}
|
|
32
|
+
const isAdminTagRequired = configService.isAdminTagRequired()
|
|
33
|
+
|
|
34
|
+
if (!requireAuthentication) {
|
|
35
|
+
const mockUser: Partial<User> = { _id: "SYSTEM" }
|
|
36
|
+
req.user = mockUser as User
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 1) Try JWT first
|
|
41
|
+
try {
|
|
42
|
+
await req.jwtVerify()
|
|
43
|
+
const payload = req.user as {
|
|
44
|
+
type?: string
|
|
45
|
+
sessionId?: string
|
|
46
|
+
_id?: string
|
|
47
|
+
}
|
|
48
|
+
fastify.log.debug(
|
|
49
|
+
`[Auth] JWT verified, payload type: ${payload.type || "user"}, sessionId: ${payload.sessionId || "none"}, _id: ${payload._id || "none"}`,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if (payload.type === "workSession" && payload.sessionId) {
|
|
53
|
+
fastify.log.info(
|
|
54
|
+
`[Auth] Work session JWT detected, sessionId: ${payload.sessionId}, pluginKey: ${pluginKey}`,
|
|
55
|
+
)
|
|
56
|
+
// Work session JWT: load session and validate
|
|
57
|
+
const workSession = await loadAndValidateWorkSession(
|
|
58
|
+
fastify,
|
|
59
|
+
payload.sessionId,
|
|
60
|
+
pluginKey,
|
|
61
|
+
)
|
|
62
|
+
if (!workSession) {
|
|
63
|
+
fastify.log.warn(
|
|
64
|
+
`[Auth] Work session validation failed for sessionId: ${payload.sessionId}`,
|
|
65
|
+
)
|
|
66
|
+
return res
|
|
67
|
+
.status(401)
|
|
68
|
+
.send({ message: "Invalid or expired work session" })
|
|
69
|
+
}
|
|
70
|
+
fastify.log.info(
|
|
71
|
+
`[Auth] Work session authenticated: sessionId=${payload.sessionId}, pluginKey=${pluginKey}`,
|
|
72
|
+
)
|
|
73
|
+
req.workSession = workSession
|
|
74
|
+
req.isWorkSession = true
|
|
75
|
+
|
|
76
|
+
// Set projectId from workSession.project if available
|
|
77
|
+
if (workSession.project) {
|
|
78
|
+
req.projectId = workSession.project
|
|
79
|
+
fastify.log.debug(`[Auth] Work session projectId: ${req.projectId}`)
|
|
80
|
+
}
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
// Normal user JWT: fetch user and set req.user
|
|
84
|
+
fastify.log.debug(
|
|
85
|
+
`[Auth] Normal user JWT, fetching user: ${(req.user as any)?._id}`,
|
|
86
|
+
)
|
|
87
|
+
const { _id } = req.user
|
|
88
|
+
const adminTag = await fastify.database
|
|
89
|
+
.collection("raclette__core-tag")
|
|
90
|
+
.findOne({ title: "ADMIN", locked: true, type: "system" })
|
|
91
|
+
let isAdmin = false
|
|
92
|
+
const user = await fastify.database
|
|
93
|
+
.collection("raclette__core-user")
|
|
94
|
+
.findOne({ _id }, { projection: { password: 0, resetKey: 0 } })
|
|
95
|
+
if (user && user.tags && adminTag) {
|
|
96
|
+
user.tags.forEach((tag: string) => {
|
|
97
|
+
if (adminTag._id === tag) isAdmin = true
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
if (isAdminTagRequired && !isAdmin) {
|
|
101
|
+
fastify.log.error("User not allowed in workbench")
|
|
102
|
+
return res.status(403).send({ message: "User not allowed in workbench" })
|
|
103
|
+
}
|
|
104
|
+
if (isAdmin) user.isAdmin = true
|
|
105
|
+
if (!user) {
|
|
106
|
+
fastify.log.error("User not found")
|
|
107
|
+
return res.status(401).send({ message: "User not found" })
|
|
108
|
+
}
|
|
109
|
+
const account = await fastify.database
|
|
110
|
+
.collection("raclette__core-account")
|
|
111
|
+
.findOne({ _id: user.account })
|
|
112
|
+
user.account = account
|
|
113
|
+
req.user = user
|
|
114
|
+
// Set projectId from user's lastProjectId (for requestData.project in payload wrappers)
|
|
115
|
+
if (user.ux?.lastProjectId) {
|
|
116
|
+
req.projectId = user.ux.lastProjectId
|
|
117
|
+
fastify.log.debug(`[Auth] User projectId: ${req.projectId}`)
|
|
118
|
+
}
|
|
119
|
+
return
|
|
120
|
+
} catch (error: any) {
|
|
121
|
+
// JWT missing or invalid: try work session via header
|
|
122
|
+
fastify.log.debug(
|
|
123
|
+
`[Auth] JWT verification failed: ${error.message}, trying work session header`,
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 2) Try X-Work-Session-Id header
|
|
128
|
+
const sessionId = req.headers[WORK_SESSION_HEADER] as string | undefined
|
|
129
|
+
if (sessionId) {
|
|
130
|
+
const workSession = await loadAndValidateWorkSession(
|
|
131
|
+
fastify,
|
|
132
|
+
sessionId,
|
|
133
|
+
pluginKey,
|
|
134
|
+
)
|
|
135
|
+
if (workSession) {
|
|
136
|
+
req.workSession = workSession
|
|
137
|
+
req.isWorkSession = true
|
|
138
|
+
|
|
139
|
+
// Set projectId from workSession.project if available
|
|
140
|
+
// workaround for how projects/spaces are currently handled - tbd
|
|
141
|
+
if (workSession.project) {
|
|
142
|
+
req.projectId = workSession.project
|
|
143
|
+
}
|
|
144
|
+
return
|
|
145
|
+
} else {
|
|
146
|
+
fastify.log.warn(
|
|
147
|
+
`[Auth] Work session validation failed for header sessionId: ${sessionId}`,
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return res.status(401).send({ message: "Authentication required" })
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function loadAndValidateWorkSession(
|
|
156
|
+
fastify: FastifyInstance,
|
|
157
|
+
sessionId: string,
|
|
158
|
+
pluginKey: string,
|
|
159
|
+
): Promise<WorkSessionDoc | null> {
|
|
160
|
+
fastify.log.debug(
|
|
161
|
+
`[WorkSession Auth] Loading and validating work session: sessionId=${sessionId}, pluginKey=${pluginKey}`,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if (!sessionId || typeof sessionId !== "string") {
|
|
165
|
+
fastify.log.warn(`[WorkSession Auth] Invalid sessionId: ${sessionId}`)
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fastify.log.debug(`[WorkSession Auth] Querying database for work session`)
|
|
170
|
+
const doc = await fastify.database
|
|
171
|
+
.collection(WORK_SESSION_COLLECTION)
|
|
172
|
+
.findOne({ _id: sessionId })
|
|
173
|
+
|
|
174
|
+
if (!doc) {
|
|
175
|
+
fastify.log.warn(`[WorkSession Auth] Work session not found: ${sessionId}`)
|
|
176
|
+
return null
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!doc.isActive) {
|
|
180
|
+
fastify.log.warn(
|
|
181
|
+
`[WorkSession Auth] Work session is not active: ${sessionId}`,
|
|
182
|
+
)
|
|
183
|
+
return null
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const expiry = doc.expiryDate ? new Date(doc.expiryDate) : null
|
|
187
|
+
if (expiry && expiry.getTime() < Date.now()) {
|
|
188
|
+
fastify.log.warn(
|
|
189
|
+
`[WorkSession Auth] Work session expired: ${sessionId}, expiryDate: ${expiry.toISOString()}`,
|
|
190
|
+
)
|
|
191
|
+
return null
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Check that this plugin is part of the work session (pluginData keys are "pluginKey:widgetName")
|
|
195
|
+
const pluginData = doc.pluginData || {}
|
|
196
|
+
const pluginKeys = Object.keys(pluginData)
|
|
197
|
+
|
|
198
|
+
const hasPlugin =
|
|
199
|
+
pluginKeys.some((k) => k.startsWith(pluginKey + ":")) ||
|
|
200
|
+
Object.prototype.hasOwnProperty.call(pluginData, pluginKey)
|
|
201
|
+
|
|
202
|
+
if (!hasPlugin) {
|
|
203
|
+
fastify.log.warn(
|
|
204
|
+
`[WorkSession Auth] Plugin ${pluginKey} not found in work session pluginData: ${pluginKeys.join(", ")}`,
|
|
205
|
+
)
|
|
206
|
+
return null
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fastify.log.info(
|
|
210
|
+
`[WorkSession Auth] Work session validated successfully: sessionId=${sessionId}, pluginKey=${pluginKey}`,
|
|
211
|
+
)
|
|
212
|
+
return doc as WorkSessionDoc
|
|
213
|
+
}
|
|
6
214
|
|
|
7
215
|
export const authenticate = async (
|
|
8
216
|
req: FastifyRequest,
|
|
@@ -20,12 +228,18 @@ export const authenticate = async (
|
|
|
20
228
|
_id: "SYSTEM",
|
|
21
229
|
}
|
|
22
230
|
|
|
23
|
-
req.user = mockUser
|
|
231
|
+
req.user = mockUser as User
|
|
24
232
|
return
|
|
25
233
|
}
|
|
26
234
|
|
|
27
235
|
try {
|
|
28
236
|
await req.jwtVerify()
|
|
237
|
+
const payload = req.user as { type?: string }
|
|
238
|
+
if (payload.type === "workSession") {
|
|
239
|
+
return res
|
|
240
|
+
.status(401)
|
|
241
|
+
.send({ message: "Work session token not allowed on this route" })
|
|
242
|
+
}
|
|
29
243
|
// fetch current user data from the db
|
|
30
244
|
const { _id } = req.user
|
|
31
245
|
const adminTag = await fastify.database
|
|
@@ -73,17 +287,42 @@ export const authenticate = async (
|
|
|
73
287
|
}
|
|
74
288
|
}
|
|
75
289
|
}
|
|
76
|
-
|
|
290
|
+
export const checkPermissions = async (
|
|
291
|
+
req: FastifyRequest,
|
|
292
|
+
res: FastifyReply,
|
|
293
|
+
fastify: FastifyInstance,
|
|
294
|
+
permissions: Array<String>,
|
|
295
|
+
) => {
|
|
296
|
+
{
|
|
297
|
+
try {
|
|
298
|
+
if (!permissions.length) {
|
|
299
|
+
fastify.log.error("No permissions given for permission check")
|
|
300
|
+
throw new Error("No Permissions")
|
|
301
|
+
}
|
|
302
|
+
// TODO implement proper Permission lookup here
|
|
303
|
+
permissions.forEach((permissionStr) => {
|
|
304
|
+
if (permissionStr === "user.isAdmin") {
|
|
305
|
+
if (!req.user.isAdmin) {
|
|
306
|
+
throw new ForbiddenError("User not permitted to action")
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
})
|
|
310
|
+
} catch (error: any) {
|
|
311
|
+
fastify.log.error(error.message)
|
|
312
|
+
return res.send(error.message)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
77
316
|
export default fastifyPlugin(async (fastify: FastifyInstance, opts = {}) => {
|
|
78
317
|
// provide some sensible options
|
|
79
318
|
const defaults = {
|
|
80
319
|
secret: false,
|
|
81
320
|
decode: { complete: true },
|
|
82
321
|
sign: {
|
|
83
|
-
iss: "api.
|
|
322
|
+
iss: "api.raclettejs.info",
|
|
84
323
|
expiresIn: "30d",
|
|
85
324
|
},
|
|
86
|
-
verify: { allowedIss: "api.
|
|
325
|
+
verify: { allowedIss: "api.raclettejs.info" },
|
|
87
326
|
}
|
|
88
327
|
|
|
89
328
|
// merge proved opts with defaults
|
|
@@ -96,4 +335,16 @@ export default fastifyPlugin(async (fastify: FastifyInstance, opts = {}) => {
|
|
|
96
335
|
fastify.decorate("authenticate", (req, res) => {
|
|
97
336
|
return authenticate(req, res, fastify)
|
|
98
337
|
})
|
|
338
|
+
|
|
339
|
+
fastify.decorate(
|
|
340
|
+
"authenticatedOrWorksession",
|
|
341
|
+
(pluginKey: string) => (req: FastifyRequest, res: FastifyReply) =>
|
|
342
|
+
authenticatedOrWorksession(req, res, fastify, pluginKey),
|
|
343
|
+
)
|
|
344
|
+
fastify.decorate(
|
|
345
|
+
"checkPermissions",
|
|
346
|
+
(permissionsArr: Array<String>) =>
|
|
347
|
+
(req: FastifyRequest, res: FastifyReply) =>
|
|
348
|
+
checkPermissions(req, res, fastify, permissionsArr),
|
|
349
|
+
)
|
|
99
350
|
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify"
|
|
2
|
+
import fastifyPlugin from "fastify-plugin"
|
|
3
|
+
import rateLimit from "@fastify/rate-limit"
|
|
4
|
+
import configService from "@c/config/configService"
|
|
5
|
+
|
|
6
|
+
export default fastifyPlugin(async (fastify: FastifyInstance) => {
|
|
7
|
+
const opts = configService.getFastifyRateLimits()
|
|
8
|
+
fastify.log.info("Fastify Ratelimiter with opts " + JSON.stringify(opts))
|
|
9
|
+
await fastify.register(rateLimit, opts)
|
|
10
|
+
fastify.setNotFoundHandler(
|
|
11
|
+
{
|
|
12
|
+
preHandler: fastify.rateLimit({
|
|
13
|
+
max: 4,
|
|
14
|
+
timeWindow: 500,
|
|
15
|
+
}),
|
|
16
|
+
},
|
|
17
|
+
function (request, reply) {
|
|
18
|
+
reply.code(404).send({ why: "you do dat?" })
|
|
19
|
+
},
|
|
20
|
+
)
|
|
21
|
+
})
|
|
@@ -12,11 +12,22 @@ import { HttpService } from "../core/http/httpTypes"
|
|
|
12
12
|
import { UserDoc } from "@c/user/user.schema"
|
|
13
13
|
import { FrontendPayloadRequestData } from "@/utils/request.utils"
|
|
14
14
|
import { FastifyRequest, FastifyReply } from "fastify"
|
|
15
|
+
import type RateLimit from "@fastify/rate-limit"
|
|
16
|
+
import type { RequestWorkSession } from "@/corePlugins/raclette__core/backend/datatypes/workSession/workSession.schema"
|
|
17
|
+
|
|
18
|
+
export type { RequestWorkSession }
|
|
15
19
|
|
|
16
20
|
declare module "fastify" {
|
|
17
21
|
interface FastifyInstance {
|
|
18
22
|
database: Connection
|
|
23
|
+
rateLimit: RateLimit
|
|
19
24
|
authenticate: (req: FastifyRequest, res: FastifyReply) => Promise<void>
|
|
25
|
+
authenticatedOrWorksession: (
|
|
26
|
+
pluginKey: string,
|
|
27
|
+
) => (req: FastifyRequest, res: FastifyReply) => Promise<void>
|
|
28
|
+
checkPermissions: (
|
|
29
|
+
permissions: string,
|
|
30
|
+
) => (req: FastifyRequest, res: FastifyReply) => Promise<void>
|
|
20
31
|
wss: Socket<DefaultEventsMap, DefaultEventsMap, DefaultEventsMap, any>
|
|
21
32
|
eventbus: EventEmitter
|
|
22
33
|
cache: ValkeyService
|
|
@@ -32,6 +43,12 @@ declare module "fastify" {
|
|
|
32
43
|
database: Connection
|
|
33
44
|
user: UserDoc
|
|
34
45
|
requestParams: FrontendPayloadRequestData
|
|
46
|
+
/** Set by authenticatedOrWorksession when request is via work session */
|
|
47
|
+
workSession?: RequestWorkSession
|
|
48
|
+
/** Set by authenticatedOrWorksession when request is via work session */
|
|
49
|
+
isWorkSession?: boolean
|
|
50
|
+
/** Project ID from user (lastProjectId) or workSession.project */
|
|
51
|
+
projectId?: string
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
interface RouteOptions {
|
|
@@ -11,13 +11,16 @@ export const createRequestParams = (
|
|
|
11
11
|
req: RequestWithCommonHeaders,
|
|
12
12
|
config: FastifyContextConfig,
|
|
13
13
|
): FrontendPayloadRequestData => {
|
|
14
|
+
// Get project from req.projectId (set by auth middleware), header, or empty string
|
|
15
|
+
const projectId = (req as any).projectId ?? req.headers["current-project"] ?? ""
|
|
16
|
+
|
|
14
17
|
const requestParams = {
|
|
15
18
|
appSessionId: req.headers["app-session-id"] ?? "",
|
|
16
19
|
appRequestCode: req.headers["app-request-code"] ?? "",
|
|
17
20
|
requestId: req.id ?? "",
|
|
18
21
|
type: "",
|
|
19
22
|
broadcast: req.headers["app-broadcast-channels"] ?? false,
|
|
20
|
-
project:
|
|
23
|
+
project: projectId,
|
|
21
24
|
userId: req.user?.id,
|
|
22
25
|
options: {},
|
|
23
26
|
...config,
|
|
@@ -1 +1,15 @@
|
|
|
1
|
-
export
|
|
1
|
+
export {
|
|
2
|
+
RacletteAppMode,
|
|
3
|
+
RacletteConfig,
|
|
4
|
+
WorkbenchConfig,
|
|
5
|
+
ServiceFrontendConfig,
|
|
6
|
+
ServiceBackendConfig,
|
|
7
|
+
EnvironmentVariables,
|
|
8
|
+
VolumeDefinition,
|
|
9
|
+
CacheConfig,
|
|
10
|
+
SocketConfig,
|
|
11
|
+
RacletteModule,
|
|
12
|
+
PluginMetadata,
|
|
13
|
+
PackageSourceConfig,
|
|
14
|
+
PackageTargetsTuple,
|
|
15
|
+
PackageTarget } from "../src/types"
|
|
@@ -268,6 +268,15 @@
|
|
|
268
268
|
"@fastify/forwarded" "^3.0.0"
|
|
269
269
|
ipaddr.js "^2.1.0"
|
|
270
270
|
|
|
271
|
+
"@fastify/rate-limit@10.3.0":
|
|
272
|
+
version "10.3.0"
|
|
273
|
+
resolved "https://registry.yarnpkg.com/@fastify/rate-limit/-/rate-limit-10.3.0.tgz#3cf6a56c0e3dd18fc0a56727675d7ba1d9a9bd7b"
|
|
274
|
+
integrity sha512-eIGkG9XKQs0nyynatApA3EVrojHOuq4l6fhB4eeCk4PIOeadvOJz9/4w3vGI44Go17uaXOWEcPkaD8kuKm7g6Q==
|
|
275
|
+
dependencies:
|
|
276
|
+
"@lukeed/ms" "^2.0.2"
|
|
277
|
+
fastify-plugin "^5.0.0"
|
|
278
|
+
toad-cache "^3.7.0"
|
|
279
|
+
|
|
271
280
|
"@fastify/schedule@6.0.0":
|
|
272
281
|
version "6.0.0"
|
|
273
282
|
resolved "https://registry.yarnpkg.com/@fastify/schedule/-/schedule-6.0.0.tgz#0a82d2c49e9f2411b9aee312c5eb178b1dceb3fc"
|
|
@@ -760,10 +769,10 @@ avvio@^9.0.0:
|
|
|
760
769
|
"@fastify/error" "^4.0.0"
|
|
761
770
|
fastq "^1.17.1"
|
|
762
771
|
|
|
763
|
-
axios@1.
|
|
764
|
-
version "1.
|
|
765
|
-
resolved "https://registry.yarnpkg.com/axios/-/axios-1.
|
|
766
|
-
integrity sha512-
|
|
772
|
+
axios@1.13.2:
|
|
773
|
+
version "1.13.2"
|
|
774
|
+
resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.2.tgz#9ada120b7b5ab24509553ec3e40123521117f687"
|
|
775
|
+
integrity sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==
|
|
767
776
|
dependencies:
|
|
768
777
|
follow-redirects "^1.15.6"
|
|
769
778
|
form-data "^4.0.4"
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<link rel="icon" href="/favicon.png" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
<title>
|
|
7
|
+
<title>racletteJS - Come in and have some Cheese</title>
|
|
8
8
|
<link rel="manifest" href="./manifest.json" accept-version="*" />
|
|
9
9
|
<script>
|
|
10
10
|
window.configs = false
|
|
File without changes
|
|
@@ -29,7 +29,16 @@ const $writeDataApi = (
|
|
|
29
29
|
const _executeParams = clone(executeParams)
|
|
30
30
|
errorSubject$.next(null)
|
|
31
31
|
responseSubject$.next({})
|
|
32
|
-
|
|
32
|
+
let queryParams
|
|
33
|
+
if (Array.isArray(_executeParams)) {
|
|
34
|
+
if (Array.isArray(_params)) {
|
|
35
|
+
queryParams = _params.concat(_executeParams)
|
|
36
|
+
}
|
|
37
|
+
queryParams = _executeParams
|
|
38
|
+
} else {
|
|
39
|
+
queryParams = mergeDeepRight(_params, _executeParams)
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
if (options.responseType === "stream") {
|
|
34
43
|
return writeData(actionId, operationDefinition, queryParams, options)
|
|
35
44
|
}
|
|
@@ -389,7 +389,7 @@ const addItemDefaulAttributes = (item, user) => {
|
|
|
389
389
|
* Will store the current time to the localStorage to determine the last time we had contact with the backend
|
|
390
390
|
*/
|
|
391
391
|
const rememberLastDataUpdateRetrieval = (projectId) => {
|
|
392
|
-
const currentProject = projectId
|
|
392
|
+
const currentProject = projectId
|
|
393
393
|
|
|
394
394
|
log.api("rememberLastDataUpdateRetrieval", currentProject)
|
|
395
395
|
localStorage.setItem(`lastUpdate_${currentProject}`, new Date().toISOString())
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export default () => {
|
|
2
|
-
|
|
2
|
+
// Reduced default count for better performance
|
|
3
|
+
let snowflakes_count = 20
|
|
3
4
|
|
|
4
5
|
if (typeof total !== "undefined") {
|
|
5
6
|
snowflakes_count = total
|
|
@@ -7,11 +8,13 @@ export default () => {
|
|
|
7
8
|
|
|
8
9
|
// This function allows you to turn on and off the snow
|
|
9
10
|
const toggle_snow = () => {
|
|
10
|
-
const check_box = document.getElementById(
|
|
11
|
+
const check_box = document.getElementById(
|
|
12
|
+
"toggle_snow",
|
|
13
|
+
) as HTMLInputElement | null
|
|
11
14
|
const snowEl = document.getElementById("snow")
|
|
12
15
|
|
|
13
16
|
if (snowEl) {
|
|
14
|
-
snowEl.style.display = check_box
|
|
17
|
+
snowEl.style.display = check_box?.checked ? "block" : "none"
|
|
15
18
|
}
|
|
16
19
|
}
|
|
17
20
|
|
|
@@ -25,91 +28,106 @@ export default () => {
|
|
|
25
28
|
|
|
26
29
|
// Set CSS custom properties for each snowflake
|
|
27
30
|
const randomX = Math.random() * 100
|
|
28
|
-
const randomScale = 0.3 + Math.random() * 0.7
|
|
29
|
-
const randomDuration =
|
|
30
|
-
const randomDelay = Math.random() *
|
|
31
|
-
const randomOpacity = 0.
|
|
32
|
-
const randomAnimation = Math.floor(Math.random() *
|
|
33
|
-
|
|
34
|
-
snowflake.style.setProperty("--start-x", `${randomX}
|
|
35
|
-
snowflake.style.setProperty("--scale", randomScale)
|
|
31
|
+
const randomScale = 0.3 + Math.random() * 0.7
|
|
32
|
+
const randomDuration = 10 + Math.random() * 15 // Slower = less GPU work
|
|
33
|
+
const randomDelay = Math.random() * 15
|
|
34
|
+
const randomOpacity = 0.4 + Math.random() * 0.6
|
|
35
|
+
const randomAnimation = Math.floor(Math.random() * 3) + 1
|
|
36
|
+
|
|
37
|
+
snowflake.style.setProperty("--start-x", `${randomX}%`)
|
|
38
|
+
snowflake.style.setProperty("--scale", randomScale.toString())
|
|
36
39
|
snowflake.style.setProperty("--duration", `${randomDuration}s`)
|
|
37
40
|
snowflake.style.setProperty("--delay", `${randomDelay}s`)
|
|
38
|
-
snowflake.style.setProperty("--opacity", randomOpacity)
|
|
41
|
+
snowflake.style.setProperty("--opacity", randomOpacity.toString())
|
|
39
42
|
snowflake.style.setProperty("--animation-name", `fall-${randomAnimation}`)
|
|
40
43
|
|
|
41
44
|
fragment.appendChild(snowflake)
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
document.getElementById("snow")
|
|
47
|
+
document.getElementById("snow")?.appendChild(fragment)
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
//
|
|
50
|
+
// Highly optimized CSS with maximum performance settings
|
|
48
51
|
const createOptimizedCSS = () => {
|
|
49
52
|
const css = `
|
|
50
53
|
#snow {
|
|
51
54
|
position: fixed;
|
|
52
55
|
top: 0;
|
|
53
56
|
left: 0;
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
right: 0;
|
|
58
|
+
bottom: 0;
|
|
56
59
|
pointer-events: none;
|
|
57
60
|
z-index: 1000;
|
|
61
|
+
overflow: hidden;
|
|
62
|
+
/* Contain layout calculations */
|
|
63
|
+
contain: layout style paint;
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
.snowflake {
|
|
61
67
|
position: absolute;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
top: -10px;
|
|
69
|
+
width: 6px;
|
|
70
|
+
height: 6px;
|
|
71
|
+
background: rgba(255, 255, 255, 0.9);
|
|
65
72
|
border-radius: 50%;
|
|
66
|
-
|
|
73
|
+
|
|
74
|
+
/* Critical GPU optimizations */
|
|
67
75
|
will-change: transform;
|
|
76
|
+
transform: translateZ(0);
|
|
77
|
+
backface-visibility: hidden;
|
|
78
|
+
|
|
79
|
+
/* REMOVED box-shadow - this is a major performance killer */
|
|
80
|
+
/* Even lightweight shadows are expensive with many elements */
|
|
68
81
|
|
|
69
82
|
/* Use CSS custom properties for individual variations */
|
|
70
83
|
left: var(--start-x);
|
|
71
84
|
opacity: var(--opacity);
|
|
72
|
-
transform: scale(var(--scale));
|
|
73
85
|
animation: var(--animation-name) var(--duration) var(--delay) linear infinite;
|
|
86
|
+
|
|
87
|
+
/* Reduce repaints by using contain */
|
|
88
|
+
contain: layout style paint;
|
|
74
89
|
}
|
|
75
90
|
|
|
76
|
-
/*
|
|
91
|
+
/* Ultra-optimized animations - only transform property */
|
|
92
|
+
/* Grouped transforms reduce compositing work */
|
|
77
93
|
@keyframes fall-1 {
|
|
78
|
-
0% {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
0% {
|
|
95
|
+
transform: translate3d(0, 0, 0) scale(var(--scale));
|
|
96
|
+
}
|
|
97
|
+
100% {
|
|
98
|
+
transform: translate3d(20px, 100vh, 0) scale(var(--scale));
|
|
99
|
+
}
|
|
83
100
|
}
|
|
84
101
|
|
|
85
102
|
@keyframes fall-2 {
|
|
86
|
-
0% {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
100% {
|
|
103
|
+
0% {
|
|
104
|
+
transform: translate3d(0, 0, 0) scale(var(--scale));
|
|
105
|
+
}
|
|
106
|
+
100% {
|
|
107
|
+
transform: translate3d(-20px, 100vh, 0) scale(var(--scale));
|
|
108
|
+
}
|
|
90
109
|
}
|
|
91
110
|
|
|
92
111
|
@keyframes fall-3 {
|
|
93
|
-
0% {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
112
|
+
0% {
|
|
113
|
+
transform: translate3d(0, 0, 0) scale(var(--scale));
|
|
114
|
+
}
|
|
115
|
+
100% {
|
|
116
|
+
transform: translate3d(0, 100vh, 0) scale(var(--scale));
|
|
117
|
+
}
|
|
98
118
|
}
|
|
99
119
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
65% { transform: translateY(65vh) translateX(-8px) scale(var(--scale)); }
|
|
104
|
-
100% { transform: translateY(100vh) translateX(12px) scale(var(--scale)); }
|
|
120
|
+
/* Pause animations when snow container is hidden to save resources */
|
|
121
|
+
#snow[style*="display: none"] .snowflake {
|
|
122
|
+
animation-play-state: paused;
|
|
105
123
|
}
|
|
106
124
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
125
|
+
/* Reduce motion for users who prefer it */
|
|
126
|
+
@media (prefers-reduced-motion: reduce) {
|
|
127
|
+
.snowflake {
|
|
128
|
+
animation: none;
|
|
129
|
+
display: none;
|
|
130
|
+
}
|
|
113
131
|
}
|
|
114
132
|
`
|
|
115
133
|
|
|
@@ -142,7 +160,7 @@ export default () => {
|
|
|
142
160
|
} else {
|
|
143
161
|
// Remove existing snow
|
|
144
162
|
snowEl.remove()
|
|
145
|
-
//
|
|
163
|
+
// Remove styles as well
|
|
146
164
|
const styles = document.getElementById("snow-styles")
|
|
147
165
|
if (styles) {
|
|
148
166
|
styles.remove()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./removeEmpty"
|