@fdm-monster/server 2.0.11 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.yarn/install-state.gz +0 -0
- package/README.md +2 -1
- package/RELEASE_NOTES.MD +139 -2
- package/dist/_virtual/_virtual_controllers.js +2 -0
- package/dist/container.js +2 -0
- package/dist/container.js.map +1 -1
- package/dist/container.tokens.js +1 -0
- package/dist/container.tokens.js.map +1 -1
- package/dist/controllers/api-key.controller.js +72 -0
- package/dist/controllers/api-key.controller.js.map +1 -0
- package/dist/controllers/auth.controller.js +1 -1
- package/dist/controllers/validation/api-key-controller.validation.js +11 -0
- package/dist/controllers/validation/api-key-controller.validation.js.map +1 -0
- package/dist/data-source.js +6 -2
- package/dist/data-source.js.map +1 -1
- package/dist/entities/api-key.entity.js +60 -0
- package/dist/entities/api-key.entity.js.map +1 -0
- package/dist/entities/index.js +2 -1
- package/dist/middleware/api-key.strategy.js +45 -0
- package/dist/middleware/api-key.strategy.js.map +1 -0
- package/dist/middleware/passport.js +3 -0
- package/dist/middleware/passport.js.map +1 -1
- package/dist/migrations/1778446203015-AddApiKey.js +49 -0
- package/dist/migrations/1778446203015-AddApiKey.js.map +1 -0
- package/dist/server.constants.js +1 -1
- package/dist/server.constants.js.map +1 -1
- package/dist/server.core.js +6 -2
- package/dist/server.core.js.map +1 -1
- package/dist/services/interfaces/api-key.dto.js +19 -0
- package/dist/services/interfaces/api-key.dto.js.map +1 -0
- package/dist/services/interfaces/api-key.service.interface.js +1 -0
- package/dist/services/interfaces/user.dto.js +2 -0
- package/dist/services/interfaces/user.dto.js.map +1 -1
- package/dist/services/orm/api-key.service.js +90 -0
- package/dist/services/orm/api-key.service.js.map +1 -0
- package/package.json +9 -9
- package/packages/consoles/package.json +3 -3
package/.yarn/install-state.gz
CHANGED
|
Binary file
|
package/README.md
CHANGED
|
@@ -102,7 +102,7 @@ Thanks to the following parties for their financial support:
|
|
|
102
102
|
These are the people involved in the project. Find the meaning of the emoji keys [here](https://allcontributors.org/docs/en/emoji-key).
|
|
103
103
|
|
|
104
104
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
105
|
-
[](#contributors-)
|
|
106
106
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
107
107
|
|
|
108
108
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
@@ -139,6 +139,7 @@ These are the people involved in the project. Find the meaning of the emoji keys
|
|
|
139
139
|
<td align="center" valign="top" width="10%"><a href="https://github.com/nuvious"><img src="https://avatars.githubusercontent.com/u/5287736?v=4?s=40" width="40px;" alt="David Cheeseman"/><br /><sub><b>David Cheeseman</b></sub></a><br /><a href="#ideas-nuvious" title="Ideas, Planning, & Feedback">🤔</a> <a href="#platform-nuvious" title="Packaging/porting to new platform">📦</a> <a href="#infra-nuvious" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
|
140
140
|
<td align="center" valign="top" width="10%"><a href="https://github.com/thatguy-jaysenodell"><img src="https://avatars.githubusercontent.com/u/118288184?v=4?s=40" width="40px;" alt="That Guy Jaysen"/><br /><sub><b>That Guy Jaysen</b></sub></a><br /><a href="https://github.com/fdm-monster/fdm-monster/issues?q=author%3Athatguy-jaysenodell" title="Bug reports">🐛</a> <a href="#ideas-thatguy-jaysenodell" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/fdm-monster/fdm-monster/commits?author=thatguy-jaysenodell" title="Code">💻</a></td>
|
|
141
141
|
<td align="center" valign="top" width="10%"><a href="http://design.zagethy.com"><img src="https://avatars.githubusercontent.com/u/6075740?v=4?s=40" width="40px;" alt="Zagethy"/><br /><sub><b>Zagethy</b></sub></a><br /><a href="https://github.com/fdm-monster/fdm-monster/issues?q=author%3AZagethy" title="Bug reports">🐛</a></td>
|
|
142
|
+
<td align="center" valign="top" width="10%"><a href="https://github.com/raykholo"><img src="https://avatars.githubusercontent.com/u/7330584?v=4?s=40" width="40px;" alt="raykholo"/><br /><sub><b>raykholo</b></sub></a><br /><a href="https://github.com/fdm-monster/fdm-monster/commits?author=raykholo" title="Code">💻</a> <a href="https://github.com/fdm-monster/fdm-monster/issues?q=author%3Araykholo" title="Bug reports">🐛</a></td>
|
|
142
143
|
</tr>
|
|
143
144
|
</tbody>
|
|
144
145
|
</table>
|
package/RELEASE_NOTES.MD
CHANGED
|
@@ -1,7 +1,141 @@
|
|
|
1
|
-
#
|
|
1
|
+
# FDM Monster 13/05/2026 2.1.0
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Features
|
|
4
|
+
|
|
5
|
+
- User-managed API keys for programmatic access with SHA-256 verification, per-user scope, and role inheritance
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
- Update client to 2.4.0 with higher version constraint
|
|
10
|
+
|
|
11
|
+
# FDM Monster 19/03/2025 2.0.11
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- Move to vitest, vite, viteplus and esm for modern testing and build tooling
|
|
16
|
+
|
|
17
|
+
## Fixes
|
|
18
|
+
|
|
19
|
+
- Bambu error flag now driven from gcode_state instead of stale print_error
|
|
20
|
+
- Controllers loaded using vite plugin
|
|
21
|
+
- Docker build process refactored with reusable workflows
|
|
22
|
+
|
|
23
|
+
## Chore
|
|
24
|
+
|
|
25
|
+
- Speed up PR build docker x64 only
|
|
26
|
+
|
|
27
|
+
# FDM Monster 2.0.10
|
|
28
|
+
|
|
29
|
+
## Fixes
|
|
30
|
+
|
|
31
|
+
- Reject duplicate filename uploads in central file storage (fixes #4972)
|
|
32
|
+
- Added ConflictException for 409 status code responses
|
|
33
|
+
- Refactored printer-files upload handler into focused helper methods
|
|
34
|
+
- Replaced manual error responses with proper exception classes throughout file upload endpoints
|
|
35
|
+
- The printer files load task was removed because it is no longer required
|
|
36
|
+
|
|
37
|
+
# FDM Monster 11/11/2024 1.7.2
|
|
38
|
+
|
|
39
|
+
## Fixes:
|
|
40
|
+
|
|
41
|
+
- API & Service validators: adjust max length of apiKey property validation to 43 to allow new `secrets` based OctoPrint api keys
|
|
42
|
+
|
|
43
|
+
# FDM Monster 04/11/2024 1.7.1
|
|
44
|
+
|
|
45
|
+
## Changes:
|
|
46
|
+
|
|
47
|
+
- Remove PrinterStateUpdatePollTask, it was slowing development down
|
|
48
|
+
- Stopped pushing to old davidzwa dockerhub organization
|
|
49
|
+
|
|
50
|
+
## Fixes:
|
|
51
|
+
|
|
52
|
+
- Octoprint does not require api key, but moonraker does not. Adjust API validation.
|
|
53
|
+
- Only printers which OctoPrint controlled need a USB silence check for restoring the websocket connectivity
|
|
54
|
+
- ApiKey requiredNotIf validation does not work (?)
|
|
55
|
+
- Batch service: missing implementation for batch get settings and batch connect
|
|
56
|
+
- Batch service: get and submit moonraker reprint based on history and webhooks objects
|
|
57
|
+
- Printer current state update task missed information about printer job and progress, caused null progress/job state
|
|
58
|
+
- Test connection: Moonraker adapter would not hook required events for connectivity tests
|
|
59
|
+
- Docker ARM64 release now tracks main instead of develop (and 1, 1.x and 1.x.y versions)
|
|
60
|
+
|
|
61
|
+
# FDM Monster 01/11/2024 1.7.0
|
|
62
|
+
|
|
63
|
+
## Chore:
|
|
64
|
+
|
|
65
|
+
- Workflows: upgrade github actions to node 20
|
|
66
|
+
- Introduce nock to test everything (except for octokit based tests)
|
|
67
|
+
|
|
68
|
+
## Features:
|
|
69
|
+
|
|
70
|
+
- Setting: add experimental moonraker support setting to model, validation and API endpoints
|
|
71
|
+
- Setting: expose experimental typeorm setting and adjust test
|
|
72
|
+
- Apply moonraker setting to middleware, and disable moonraker printers on disabling feature
|
|
73
|
+
- Enable moonraker printerType support
|
|
74
|
+
|
|
75
|
+
## Fixes:
|
|
76
|
+
|
|
77
|
+
- Add sourceMaps: "inline" to jest for hitting breakpoints with swc in Webstorm
|
|
78
|
+
- Printer file clean could contain extra data, skim those props off
|
|
79
|
+
- Batch reprint selection: wrong status is concluded when reprint preparation is called (OctoPrint is not available, instead of no job is selected)
|
|
80
|
+
|
|
81
|
+
# FDM Monster 12/26/2024 1.6.4
|
|
82
|
+
|
|
83
|
+
## Changes:
|
|
84
|
+
|
|
85
|
+
- Dropped printer file database table (kept the entity/schema), so FDMM "remote file" architecture can be redesigned. We're solely using in-memory cache from this point.
|
|
86
|
+
- Introduced first version of the standardized PrinterApi, the concept of multiplePrinterServices with only OctoPrint and printer type to indicate service types in the future
|
|
87
|
+
- Simplified the PrinterFile model, dropped unused properties for multi-print-service standardization in the future.
|
|
88
|
+
- Used attributes for PrinterFilesController
|
|
89
|
+
- Renamed OctoPrintApiService to OctoprintClient
|
|
90
|
+
- Switch to 1.6.x client UI version range
|
|
91
|
+
- Update Octoprint websocket with refresh interval, defaulting to 10sec. This loop will keep refresh attempts internal to the Websocket adapter of each service type (Octoprint, Moonraker, etc).
|
|
92
|
+
|
|
93
|
+
## Fixes:
|
|
94
|
+
|
|
95
|
+
- Print completion service would throw exception that correlationId was not provided (SQLite didn't like that). This error is prevented.
|
|
96
|
+
- Moonraker type printers will resort to OctoPrint implementation (edge case in development)
|
|
97
|
+
- Yaml import service will not assume printerType is provided, therefore defaulting to OctoprintType for older FDM Monster versions
|
|
98
|
+
- Reprint will now take selected job instead of scanning the unreliable file list for latest print date
|
|
99
|
+
|
|
100
|
+
# FDM Monster 14/06/2024 1.6.3
|
|
101
|
+
|
|
102
|
+
## Fixes:
|
|
103
|
+
|
|
104
|
+
- Add .yarn folder to FDM Monster release .zip file on github release
|
|
105
|
+
- SQLite: Role entity name column should be unique
|
|
106
|
+
- OctoPrint: websocket authentication would fail when multiple users are present. Fixed the username lookup with current-user OctoPrint API endpoint
|
|
107
|
+
|
|
108
|
+
## Features:
|
|
109
|
+
|
|
110
|
+
- Moonraker: prep work by modeling the moonraker api and websocket events
|
|
111
|
+
|
|
112
|
+
# FDM Monster 13/04/2024 1.6.2
|
|
113
|
+
|
|
114
|
+
## Features:
|
|
115
|
+
|
|
116
|
+
- Add GitHub Rate Limit API endpoint GET /api/server/github-rate-limit
|
|
117
|
+
- Add feature flag for GitHub Rate Limit API endpoint
|
|
118
|
+
|
|
119
|
+
## Changes:
|
|
120
|
+
|
|
121
|
+
- Dropped the permission check on /api/features as it made no sense
|
|
122
|
+
- Anonymized logging
|
|
123
|
+
- Handle OctoKit errors (ExternalServiceErrors) differently than OctoPrint errors (different HttpClient implementations). Refer to the new rate limit API and feature flag.
|
|
124
|
+
- Set default API call timeout to 10000 milliseconds (to call OctoPrint APIs)
|
|
4
125
|
|
|
126
|
+
## Fixes:
|
|
127
|
+
|
|
128
|
+
- YAML Import would fail updating properly an existing floor by floor level
|
|
129
|
+
- YAML Import has issues updating a floor, printers positions are not consistently are updated.
|
|
130
|
+
- YAML Import converted printer IDs to string, causing the printers to not show up on the printer grid until server restart. The import was done correctly on database level.
|
|
131
|
+
- Deleting a printer would remove the position of another printer, the removal was referring to the wrong position.
|
|
132
|
+
- Setting demo mode will not set wizard to be completed: first time setup will be required after setting demo mode to false.
|
|
133
|
+
- Settings: incorrect file clean shape would not throw any validation errors (SQLite only). Validation has been added for file clean on API level.
|
|
134
|
+
- Settings: make all settings API endpoints stricter by adding validation on API level. Patching is not possible anymore.
|
|
135
|
+
- Printer: add OctoPrint URL validation which parses the error with user friendly errors as result
|
|
136
|
+
|
|
137
|
+
- Add set user roles API to avoid users staying in limbo with GUEST role after verification.
|
|
138
|
+
- User API: apply consistent string number coercion in user API to avoid unexpected ID comparison
|
|
5
139
|
## Features
|
|
6
140
|
|
|
7
141
|
- Move to vitest, vite, viteplus and esm
|
|
@@ -486,3 +620,6 @@ Hotfix release
|
|
|
486
620
|
- Settings: incorrect file clean shape would not throw any validation errors (SQLite only). Validation has been added for file clean on API level.
|
|
487
621
|
- Settings: make all settings API endpoints stricter by adding validation on API level. Patching is not possible anymore.
|
|
488
622
|
- Printer: add OctoPrint URL validation which parses the error with user friendly errors as result
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { api_key_controller_exports } from "../controllers/api-key.controller.js";
|
|
1
2
|
import { auth_controller_exports } from "../controllers/auth.controller.js";
|
|
2
3
|
import { batch_call_controller_exports } from "../controllers/batch-call.controller.js";
|
|
3
4
|
import { camera_stream_controller_exports } from "../controllers/camera-stream.controller.js";
|
|
@@ -19,6 +20,7 @@ import { slicer_compat_controller_exports } from "../controllers/slicer-compat.c
|
|
|
19
20
|
import { user_controller_exports } from "../controllers/user.controller.js";
|
|
20
21
|
//#region \0virtual:controllers
|
|
21
22
|
var _virtual_controllers_default = [
|
|
23
|
+
api_key_controller_exports,
|
|
22
24
|
auth_controller_exports,
|
|
23
25
|
batch_call_controller_exports,
|
|
24
26
|
camera_stream_controller_exports,
|
package/dist/container.js
CHANGED
|
@@ -47,6 +47,7 @@ import { CameraStreamService } from "./services/orm/camera-stream.service.js";
|
|
|
47
47
|
import { JwtService } from "./services/authentication/jwt.service.js";
|
|
48
48
|
import { AuthService } from "./services/authentication/auth.service.js";
|
|
49
49
|
import { RefreshTokenService } from "./services/orm/refresh-token.service.js";
|
|
50
|
+
import { ApiKeyService } from "./services/orm/api-key.service.js";
|
|
50
51
|
import { SettingsService } from "./services/orm/settings.service.js";
|
|
51
52
|
import { FloorService } from "./services/orm/floor.service.js";
|
|
52
53
|
import { FloorPositionService } from "./services/orm/floor-position.service.js";
|
|
@@ -92,6 +93,7 @@ function configureContainer() {
|
|
|
92
93
|
[di.printerMaintenanceLogService]: asClass(PrinterMaintenanceLogService),
|
|
93
94
|
[di.printerTagService]: asClass(PrinterTagService),
|
|
94
95
|
[di.refreshTokenService]: asClass(RefreshTokenService).singleton(),
|
|
96
|
+
[di.apiKeyService]: asClass(ApiKeyService).singleton(),
|
|
95
97
|
[di.userService]: asClass(UserService).singleton(),
|
|
96
98
|
[di.userRoleService]: asClass(UserRoleService).singleton(),
|
|
97
99
|
[di.roleService]: asClass(RoleService).singleton(),
|
package/dist/container.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.js","names":[],"sources":["../src/container.ts"],"sourcesContent":["import { Octokit } from \"octokit\";\nimport { asClass, asFunction, asValue, createContainer, InjectionMode } from \"awilix\";\nimport { ToadScheduler } from \"toad-scheduler\";\nimport { DITokens } from \"./container.tokens\";\nimport { PrinterService } from \"./services/orm/printer.service\";\nimport { PrinterMaintenanceLogService } from \"./services/orm/printer-maintenance-log.service\";\nimport { SettingsStore } from \"./state/settings.store\";\nimport { ServerReleaseService } from \"./services/core/server-release.service\";\nimport { TaskManagerService } from \"./services/task-manager.service\";\nimport { GithubService } from \"./services/core/github.service\";\nimport { PrinterWebsocketTask } from \"./tasks/printer-websocket.task\";\nimport { SocketIoTask } from \"./tasks/socketio.task\";\nimport { SocketFactory } from \"./services/socket.factory\";\nimport { configureEventEmitter } from \"./handlers/event-emitter\";\nimport { AppConstants } from \"./server.constants\";\nimport { SoftwareUpdateTask } from \"./tasks/software-update.task\";\nimport { LoggerFactory } from \"./handlers/logger-factory\";\nimport { MulterService } from \"./services/core/multer.service\";\nimport { FileUploadTrackerCache } from \"./state/file-upload-tracker.cache\";\nimport { ServerHost } from \"./server.host\";\nimport { BootTask } from \"./tasks/boot.task\";\nimport { UserService } from \"./services/orm/user.service\";\nimport { RoleService } from \"./services/orm/role.service\";\nimport { PermissionService } from \"./services/orm/permission.service\";\nimport { ROLES } from \"./constants/authorization.constants\";\nimport { PrinterWebsocketRestoreTask } from \"./tasks/printer-websocket-restore.task\";\nimport { ConfigService, type IConfigService } from \"./services/core/config.service\";\nimport { SocketIoGateway } from \"./state/socket-io.gateway\";\nimport { ClientBundleService } from \"./services/core/client-bundle.service\";\nimport { FloorStore } from \"./state/floor.store\";\nimport { YamlService } from \"./services/core/yaml.service\";\nimport { MonsterPiService } from \"./services/core/monsterpi.service\";\nimport { BatchCallService } from \"./services/core/batch-call.service\";\nimport { ClientDistDownloadTask } from \"./tasks/client-bundle.task\";\nimport { OctoprintWebsocketAdapter } from \"./services/octoprint/octoprint-websocket.adapter\";\nimport { PrinterCache } from \"./state/printer.cache\";\nimport { PrinterSocketStore } from \"./state/printer-socket.store\";\nimport { TestPrinterSocketStore } from \"./state/test-printer-socket.store\";\nimport { PrinterEventsCache } from \"./state/printer-events.cache\";\nimport { LogDumpService } from \"./services/core/logs-manager.service\";\nimport { CameraStreamService } from \"./services/orm/camera-stream.service\";\nimport { JwtService } from \"./services/authentication/jwt.service\";\nimport { AuthService } from \"./services/authentication/auth.service\";\nimport { throttling, ThrottlingOptions } from \"@octokit/plugin-throttling\";\nimport { RefreshTokenService } from \"@/services/orm/refresh-token.service\";\nimport { SettingsService } from \"@/services/orm/settings.service\";\nimport { FloorService } from \"@/services/orm/floor.service\";\nimport { FloorPositionService } from \"@/services/orm/floor-position.service\";\nimport { ExceptionFilter } from \"@/middleware/exception.filter\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport { UserRoleService } from \"@/services/orm/user-role.service\";\nimport { PrinterTagService } from \"@/services/orm/printer-tag.service\";\nimport { MoonrakerClient } from \"@/services/moonraker/moonraker.client\";\nimport { MoonrakerWebsocketAdapter } from \"@/services/moonraker/moonraker-websocket.adapter\";\nimport { OctoprintApi } from \"@/services/octoprint.api\";\nimport { OctoprintClient } from \"@/services/octoprint/octoprint.client\";\nimport { MoonrakerApi } from \"@/services/moonraker.api\";\nimport { PrinterApiFactory } from \"@/services/printer-api.factory\";\nimport { PrinterThumbnailCache } from \"@/state/printer-thumbnail.cache\";\nimport { HttpClientFactory } from \"@/services/core/http-client.factory\";\nimport { CradleService } from \"@/services/core/cradle.service\";\nimport { PrusaLinkApi } from \"@/services/prusa-link/prusa-link.api\";\nimport { PrusaLinkHttpPollingAdapter } from \"@/services/prusa-link/prusa-link-http-polling.adapter\";\nimport { BambuClient } from \"@/services/bambu/bambu.client\";\nimport { BambuMqttAdapter } from \"@/services/bambu/bambu-mqtt.adapter\";\nimport { BambuFtpAdapter } from \"@/services/bambu/bambu-ftp.adapter\";\nimport { BambuApi } from \"@/services/bambu.api\";\nimport { PrintQueueService } from \"@/services/print-queue.service\";\nimport { FileStorageService } from \"@/services/file-storage.service\";\nimport { PrintJobService } from \"@/services/orm/print-job.service\";\nimport { FileAnalysisService } from \"@/services/file-analysis.service\";\nimport { PrintJobAnalysisTask } from \"@/tasks/print-job-analysis.task\";\nimport { PrintFileDownloaderService } from \"@/services/print-file-downloader.service\";\n\nexport function configureContainer() {\n const container = createContainer({\n injectionMode: InjectionMode.CLASSIC,\n });\n\n const di = DITokens;\n\n container.register({\n // -- asValue/asFunction constants --\n [di.appDefaultRole]: asValue(ROLES.GUEST),\n [di.appDefaultRoleNoLogin]: asValue(ROLES.ADMIN),\n [di.serverVersion]: asFunction(() => {\n return process.env[AppConstants.VERSION_KEY];\n }),\n [di.cradleService]: asClass(CradleService).inject((container) => ({ container })),\n [di.socketFactory]: asClass(SocketFactory).transient(), // Factory function, transient on purpose!\n\n // V1.6.0 capable services\n [di.typeormService]: asClass(TypeormService).singleton(),\n [di.settingsService]: asClass(SettingsService),\n [di.floorService]: asClass(FloorService).singleton(),\n [di.floorPositionService]: asClass(FloorPositionService).singleton(),\n [di.cameraStreamService]: asClass(CameraStreamService).singleton(),\n [di.printerService]: asClass(PrinterService),\n [di.printerMaintenanceLogService]: asClass(PrinterMaintenanceLogService),\n [di.printerTagService]: asClass(PrinterTagService),\n [di.refreshTokenService]: asClass(RefreshTokenService).singleton(),\n [di.userService]: asClass(UserService).singleton(),\n [di.userRoleService]: asClass(UserRoleService).singleton(),\n [di.roleService]: asClass(RoleService).singleton(),\n [di.permissionService]: asClass(PermissionService).singleton(),\n // -- asClass --\n [di.serverHost]: asClass(ServerHost).singleton(),\n [di.exceptionFilter]: asClass(ExceptionFilter).singleton(),\n [di.settingsStore]: asClass(SettingsStore).singleton(),\n [di.configService]: asClass(ConfigService),\n [di.authService]: asClass(AuthService).singleton(),\n [di.jwtService]: asClass(JwtService).singleton(),\n\n [di.loggerFactory]: asFunction(LoggerFactory).transient(),\n [di.taskManagerService]: asClass(TaskManagerService).singleton(),\n [di.toadScheduler]: asClass(ToadScheduler).singleton(),\n [di.eventEmitter2]: asFunction(configureEventEmitter).singleton(),\n [di.serverReleaseService]: asClass(ServerReleaseService).singleton(),\n [di.monsterPiService]: asClass(MonsterPiService).singleton(),\n [di.githubService]: asClass(GithubService),\n [di.octokitService]: asFunction((configService: IConfigService) => {\n const CustomOctoKit = Octokit.plugin(throttling);\n return new CustomOctoKit({\n auth: configService.get(AppConstants.GITHUB_PAT),\n throttle: {\n onRateLimit: (_retryAfter, options, _octokit, _retryCount) => {\n const logger = LoggerFactory()(\"OctoKitThrottle\");\n logger.warn(`Request quota exhausted for request ${options.method} ${options.url}`);\n },\n onSecondaryRateLimit: (_retryAfter, options, _octokit) => {\n const logger = LoggerFactory()(\"OctoKitThrottle\");\n // does not retry, only logs a warning\n logger.warn(`SecondaryRateLimit detected for request ${options.method} ${options.url}`);\n },\n } satisfies ThrottlingOptions,\n });\n }),\n [di.clientBundleService]: asClass(ClientBundleService),\n [di.logDumpService]: asClass(LogDumpService),\n [di.httpClientFactory]: asClass(HttpClientFactory),\n [di.socketIoGateway]: asClass(SocketIoGateway).singleton(),\n [di.multerService]: asClass(MulterService).singleton(),\n [di.yamlService]: asClass(YamlService),\n [di.printerLogin]: asValue(null), // Fallback when no scope is provided\n [di.printerApiFactory]: asClass(PrinterApiFactory).transient(), // Factory function, transient on purpose!\n [di.prusaLinkApi]: asClass(PrusaLinkApi).transient(), // Transient on purpose\n [di.prusaLinkPollingAdapter]: asClass(PrusaLinkHttpPollingAdapter).transient(), // Transient on purpose\n [di.octoprintApi]: asClass(OctoprintApi).transient(), // Transient on purpose\n [di.octoprintClient]: asClass(OctoprintClient).singleton(),\n [di.octoPrintSockIoAdapter]: asClass(OctoprintWebsocketAdapter).transient(), // Transient on purpose\n [di.moonrakerApi]: asClass(MoonrakerApi).transient(), // Transient on purpose\n [di.moonrakerClient]: asClass(MoonrakerClient).singleton(),\n [di.moonrakerWebsocketAdapter]: asClass(MoonrakerWebsocketAdapter).transient(), // Transient on purpose\n [di.bambuApi]: asClass(BambuApi).transient(),\n [di.bambuClient]: asClass(BambuClient).transient(), // Transient for multi-printer support\n [di.bambuFtpAdapter]: asClass(BambuFtpAdapter).transient(),\n [di.bambuMqttAdapter]: asClass(BambuMqttAdapter).transient(),\n [di.batchCallService]: asClass(BatchCallService).singleton(),\n\n [di.floorStore]: asClass(FloorStore).singleton(),\n [di.printerThumbnailCache]: asClass(PrinterThumbnailCache).singleton(),\n [di.fileUploadTrackerCache]: asClass(FileUploadTrackerCache).singleton(),\n [di.printerCache]: asClass(PrinterCache).singleton(),\n [di.printerEventsCache]: asClass(PrinterEventsCache).singleton(),\n [di.printerSocketStore]: asClass(PrinterSocketStore).singleton(),\n [di.testPrinterSocketStore]: asClass(TestPrinterSocketStore).singleton(),\n [di.printJobService]: asClass(PrintJobService).singleton(),\n [di.printQueueService]: asClass(PrintQueueService).singleton(),\n [di.fileStorageService]: asClass(FileStorageService).singleton(),\n [di.fileAnalysisService]: asClass(FileAnalysisService).singleton(),\n [di.printFileDownloaderService]: asClass(PrintFileDownloaderService).singleton(),\n\n [di.bootTask]: asClass(BootTask),\n [di.softwareUpdateTask]: asClass(SoftwareUpdateTask), // Provided SSE handlers (couplers) shared with controllers\n [di.socketIoTask]: asClass(SocketIoTask).singleton(), // This task is a quick task (~100ms per printer)\n [di.clientDistDownloadTask]: asClass(ClientDistDownloadTask).singleton(),\n [di.printerWebsocketTask]: asClass(PrinterWebsocketTask).singleton(), // This task is a recurring heartbeat task\n [di.printerWebsocketRestoreTask]: asClass(PrinterWebsocketRestoreTask).singleton(), // Task aimed at testing the printer API\n [di.printJobAnalysisTask]: asClass(PrintJobAnalysisTask).singleton(),\n });\n\n return container;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"container.js","names":[],"sources":["../src/container.ts"],"sourcesContent":["import { Octokit } from \"octokit\";\nimport { asClass, asFunction, asValue, createContainer, InjectionMode } from \"awilix\";\nimport { ToadScheduler } from \"toad-scheduler\";\nimport { DITokens } from \"./container.tokens\";\nimport { PrinterService } from \"./services/orm/printer.service\";\nimport { PrinterMaintenanceLogService } from \"./services/orm/printer-maintenance-log.service\";\nimport { SettingsStore } from \"./state/settings.store\";\nimport { ServerReleaseService } from \"./services/core/server-release.service\";\nimport { TaskManagerService } from \"./services/task-manager.service\";\nimport { GithubService } from \"./services/core/github.service\";\nimport { PrinterWebsocketTask } from \"./tasks/printer-websocket.task\";\nimport { SocketIoTask } from \"./tasks/socketio.task\";\nimport { SocketFactory } from \"./services/socket.factory\";\nimport { configureEventEmitter } from \"./handlers/event-emitter\";\nimport { AppConstants } from \"./server.constants\";\nimport { SoftwareUpdateTask } from \"./tasks/software-update.task\";\nimport { LoggerFactory } from \"./handlers/logger-factory\";\nimport { MulterService } from \"./services/core/multer.service\";\nimport { FileUploadTrackerCache } from \"./state/file-upload-tracker.cache\";\nimport { ServerHost } from \"./server.host\";\nimport { BootTask } from \"./tasks/boot.task\";\nimport { UserService } from \"./services/orm/user.service\";\nimport { RoleService } from \"./services/orm/role.service\";\nimport { PermissionService } from \"./services/orm/permission.service\";\nimport { ROLES } from \"./constants/authorization.constants\";\nimport { PrinterWebsocketRestoreTask } from \"./tasks/printer-websocket-restore.task\";\nimport { ConfigService, type IConfigService } from \"./services/core/config.service\";\nimport { SocketIoGateway } from \"./state/socket-io.gateway\";\nimport { ClientBundleService } from \"./services/core/client-bundle.service\";\nimport { FloorStore } from \"./state/floor.store\";\nimport { YamlService } from \"./services/core/yaml.service\";\nimport { MonsterPiService } from \"./services/core/monsterpi.service\";\nimport { BatchCallService } from \"./services/core/batch-call.service\";\nimport { ClientDistDownloadTask } from \"./tasks/client-bundle.task\";\nimport { OctoprintWebsocketAdapter } from \"./services/octoprint/octoprint-websocket.adapter\";\nimport { PrinterCache } from \"./state/printer.cache\";\nimport { PrinterSocketStore } from \"./state/printer-socket.store\";\nimport { TestPrinterSocketStore } from \"./state/test-printer-socket.store\";\nimport { PrinterEventsCache } from \"./state/printer-events.cache\";\nimport { LogDumpService } from \"./services/core/logs-manager.service\";\nimport { CameraStreamService } from \"./services/orm/camera-stream.service\";\nimport { JwtService } from \"./services/authentication/jwt.service\";\nimport { AuthService } from \"./services/authentication/auth.service\";\nimport { throttling, ThrottlingOptions } from \"@octokit/plugin-throttling\";\nimport { RefreshTokenService } from \"@/services/orm/refresh-token.service\";\nimport { ApiKeyService } from \"@/services/orm/api-key.service\";\nimport { SettingsService } from \"@/services/orm/settings.service\";\nimport { FloorService } from \"@/services/orm/floor.service\";\nimport { FloorPositionService } from \"@/services/orm/floor-position.service\";\nimport { ExceptionFilter } from \"@/middleware/exception.filter\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport { UserRoleService } from \"@/services/orm/user-role.service\";\nimport { PrinterTagService } from \"@/services/orm/printer-tag.service\";\nimport { MoonrakerClient } from \"@/services/moonraker/moonraker.client\";\nimport { MoonrakerWebsocketAdapter } from \"@/services/moonraker/moonraker-websocket.adapter\";\nimport { OctoprintApi } from \"@/services/octoprint.api\";\nimport { OctoprintClient } from \"@/services/octoprint/octoprint.client\";\nimport { MoonrakerApi } from \"@/services/moonraker.api\";\nimport { PrinterApiFactory } from \"@/services/printer-api.factory\";\nimport { PrinterThumbnailCache } from \"@/state/printer-thumbnail.cache\";\nimport { HttpClientFactory } from \"@/services/core/http-client.factory\";\nimport { CradleService } from \"@/services/core/cradle.service\";\nimport { PrusaLinkApi } from \"@/services/prusa-link/prusa-link.api\";\nimport { PrusaLinkHttpPollingAdapter } from \"@/services/prusa-link/prusa-link-http-polling.adapter\";\nimport { BambuClient } from \"@/services/bambu/bambu.client\";\nimport { BambuMqttAdapter } from \"@/services/bambu/bambu-mqtt.adapter\";\nimport { BambuFtpAdapter } from \"@/services/bambu/bambu-ftp.adapter\";\nimport { BambuApi } from \"@/services/bambu.api\";\nimport { PrintQueueService } from \"@/services/print-queue.service\";\nimport { FileStorageService } from \"@/services/file-storage.service\";\nimport { PrintJobService } from \"@/services/orm/print-job.service\";\nimport { FileAnalysisService } from \"@/services/file-analysis.service\";\nimport { PrintJobAnalysisTask } from \"@/tasks/print-job-analysis.task\";\nimport { PrintFileDownloaderService } from \"@/services/print-file-downloader.service\";\n\nexport function configureContainer() {\n const container = createContainer({\n injectionMode: InjectionMode.CLASSIC,\n });\n\n const di = DITokens;\n\n container.register({\n // -- asValue/asFunction constants --\n [di.appDefaultRole]: asValue(ROLES.GUEST),\n [di.appDefaultRoleNoLogin]: asValue(ROLES.ADMIN),\n [di.serverVersion]: asFunction(() => {\n return process.env[AppConstants.VERSION_KEY];\n }),\n [di.cradleService]: asClass(CradleService).inject((container) => ({ container })),\n [di.socketFactory]: asClass(SocketFactory).transient(), // Factory function, transient on purpose!\n\n // V1.6.0 capable services\n [di.typeormService]: asClass(TypeormService).singleton(),\n [di.settingsService]: asClass(SettingsService),\n [di.floorService]: asClass(FloorService).singleton(),\n [di.floorPositionService]: asClass(FloorPositionService).singleton(),\n [di.cameraStreamService]: asClass(CameraStreamService).singleton(),\n [di.printerService]: asClass(PrinterService),\n [di.printerMaintenanceLogService]: asClass(PrinterMaintenanceLogService),\n [di.printerTagService]: asClass(PrinterTagService),\n [di.refreshTokenService]: asClass(RefreshTokenService).singleton(),\n [di.apiKeyService]: asClass(ApiKeyService).singleton(),\n [di.userService]: asClass(UserService).singleton(),\n [di.userRoleService]: asClass(UserRoleService).singleton(),\n [di.roleService]: asClass(RoleService).singleton(),\n [di.permissionService]: asClass(PermissionService).singleton(),\n // -- asClass --\n [di.serverHost]: asClass(ServerHost).singleton(),\n [di.exceptionFilter]: asClass(ExceptionFilter).singleton(),\n [di.settingsStore]: asClass(SettingsStore).singleton(),\n [di.configService]: asClass(ConfigService),\n [di.authService]: asClass(AuthService).singleton(),\n [di.jwtService]: asClass(JwtService).singleton(),\n\n [di.loggerFactory]: asFunction(LoggerFactory).transient(),\n [di.taskManagerService]: asClass(TaskManagerService).singleton(),\n [di.toadScheduler]: asClass(ToadScheduler).singleton(),\n [di.eventEmitter2]: asFunction(configureEventEmitter).singleton(),\n [di.serverReleaseService]: asClass(ServerReleaseService).singleton(),\n [di.monsterPiService]: asClass(MonsterPiService).singleton(),\n [di.githubService]: asClass(GithubService),\n [di.octokitService]: asFunction((configService: IConfigService) => {\n const CustomOctoKit = Octokit.plugin(throttling);\n return new CustomOctoKit({\n auth: configService.get(AppConstants.GITHUB_PAT),\n throttle: {\n onRateLimit: (_retryAfter, options, _octokit, _retryCount) => {\n const logger = LoggerFactory()(\"OctoKitThrottle\");\n logger.warn(`Request quota exhausted for request ${options.method} ${options.url}`);\n },\n onSecondaryRateLimit: (_retryAfter, options, _octokit) => {\n const logger = LoggerFactory()(\"OctoKitThrottle\");\n // does not retry, only logs a warning\n logger.warn(`SecondaryRateLimit detected for request ${options.method} ${options.url}`);\n },\n } satisfies ThrottlingOptions,\n });\n }),\n [di.clientBundleService]: asClass(ClientBundleService),\n [di.logDumpService]: asClass(LogDumpService),\n [di.httpClientFactory]: asClass(HttpClientFactory),\n [di.socketIoGateway]: asClass(SocketIoGateway).singleton(),\n [di.multerService]: asClass(MulterService).singleton(),\n [di.yamlService]: asClass(YamlService),\n [di.printerLogin]: asValue(null), // Fallback when no scope is provided\n [di.printerApiFactory]: asClass(PrinterApiFactory).transient(), // Factory function, transient on purpose!\n [di.prusaLinkApi]: asClass(PrusaLinkApi).transient(), // Transient on purpose\n [di.prusaLinkPollingAdapter]: asClass(PrusaLinkHttpPollingAdapter).transient(), // Transient on purpose\n [di.octoprintApi]: asClass(OctoprintApi).transient(), // Transient on purpose\n [di.octoprintClient]: asClass(OctoprintClient).singleton(),\n [di.octoPrintSockIoAdapter]: asClass(OctoprintWebsocketAdapter).transient(), // Transient on purpose\n [di.moonrakerApi]: asClass(MoonrakerApi).transient(), // Transient on purpose\n [di.moonrakerClient]: asClass(MoonrakerClient).singleton(),\n [di.moonrakerWebsocketAdapter]: asClass(MoonrakerWebsocketAdapter).transient(), // Transient on purpose\n [di.bambuApi]: asClass(BambuApi).transient(),\n [di.bambuClient]: asClass(BambuClient).transient(), // Transient for multi-printer support\n [di.bambuFtpAdapter]: asClass(BambuFtpAdapter).transient(),\n [di.bambuMqttAdapter]: asClass(BambuMqttAdapter).transient(),\n [di.batchCallService]: asClass(BatchCallService).singleton(),\n\n [di.floorStore]: asClass(FloorStore).singleton(),\n [di.printerThumbnailCache]: asClass(PrinterThumbnailCache).singleton(),\n [di.fileUploadTrackerCache]: asClass(FileUploadTrackerCache).singleton(),\n [di.printerCache]: asClass(PrinterCache).singleton(),\n [di.printerEventsCache]: asClass(PrinterEventsCache).singleton(),\n [di.printerSocketStore]: asClass(PrinterSocketStore).singleton(),\n [di.testPrinterSocketStore]: asClass(TestPrinterSocketStore).singleton(),\n [di.printJobService]: asClass(PrintJobService).singleton(),\n [di.printQueueService]: asClass(PrintQueueService).singleton(),\n [di.fileStorageService]: asClass(FileStorageService).singleton(),\n [di.fileAnalysisService]: asClass(FileAnalysisService).singleton(),\n [di.printFileDownloaderService]: asClass(PrintFileDownloaderService).singleton(),\n\n [di.bootTask]: asClass(BootTask),\n [di.softwareUpdateTask]: asClass(SoftwareUpdateTask), // Provided SSE handlers (couplers) shared with controllers\n [di.socketIoTask]: asClass(SocketIoTask).singleton(), // This task is a quick task (~100ms per printer)\n [di.clientDistDownloadTask]: asClass(ClientDistDownloadTask).singleton(),\n [di.printerWebsocketTask]: asClass(PrinterWebsocketTask).singleton(), // This task is a recurring heartbeat task\n [di.printerWebsocketRestoreTask]: asClass(PrinterWebsocketRestoreTask).singleton(), // Task aimed at testing the printer API\n [di.printJobAnalysisTask]: asClass(PrintJobAnalysisTask).singleton(),\n });\n\n return container;\n}\n"],"mappings":"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"}
|
package/dist/container.tokens.js
CHANGED
|
@@ -36,6 +36,7 @@ const DITokens = {
|
|
|
36
36
|
userTokenService: "userTokenService",
|
|
37
37
|
authService: "authService",
|
|
38
38
|
refreshTokenService: "refreshTokenService",
|
|
39
|
+
apiKeyService: "apiKeyService",
|
|
39
40
|
userService: "userService",
|
|
40
41
|
userRoleService: "userRoleService",
|
|
41
42
|
permissionService: "permissionService",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.tokens.js","names":[],"sources":["../src/container.tokens.ts"],"sourcesContent":["export const DITokens = {\n cradleService: \"cradleService\",\n // Values\n serverVersion: \"serverVersion\",\n appDefaultRole: \"appDefaultRole\",\n appDefaultRoleNoLogin: \"appDefaultRoleNoLogin\",\n typeormService: \"typeormService\",\n // Instances\n serverHost: \"serverHost\",\n exceptionFilter: \"exceptionFilter\",\n loggerFactory: \"loggerFactory\",\n httpClientFactory: \"httpClientFactory\",\n socketIoGateway: \"socketIoGateway\",\n multerService: \"multerService\",\n configService: \"configService\",\n taskManagerService: \"taskManagerService\",\n toadScheduler: \"toadScheduler\",\n eventEmitter2: \"eventEmitter2\",\n printerService: \"printerService\",\n printerTagService: \"printerTagService\",\n printerMaintenanceLogService: \"printerMaintenanceLogService\",\n floorPositionService: \"floorPositionService\",\n printJobService: \"printJobService\",\n printQueueService: \"printQueueService\",\n fileStorageService: \"fileStorageService\",\n fileAnalysisService: \"fileAnalysisService\",\n printFileDownloaderService: \"printFileDownloaderService\",\n floorService: \"floorService\",\n yamlService: \"yamlService\",\n settingsService: \"settingsService\",\n serverReleaseService: \"serverReleaseService\",\n monsterPiService: \"monsterPiService\",\n githubService: \"githubService\",\n octokitService: \"octokitService\",\n clientBundleService: \"clientBundleService\",\n logDumpService: \"logDumpService\",\n userTokenService: \"userTokenService\",\n authService: \"authService\",\n refreshTokenService: \"refreshTokenService\",\n userService: \"userService\",\n userRoleService: \"userRoleService\",\n permissionService: \"permissionService\",\n jwtService: \"jwtService\",\n roleService: \"roleService\",\n prusaLinkApi: \"prusaLinkApi\",\n octoprintApi: \"octoprintApi\",\n octoprintClient: \"octoprintClient\",\n moonrakerApi: \"moonrakerApi\",\n moonrakerClient: \"moonrakerClient\",\n bambuApi: \"bambuApi\",\n bambuClient: \"bambuClient\",\n bambuFtpAdapter: \"bambuFtpAdapter\",\n bambuMqttAdapter: \"bambuMqttAdapter\",\n cameraStreamService: \"cameraStreamService\",\n socketFactory: \"socketFactory\",\n printerApiFactory: \"printerApiFactory\",\n printerLogin: \"printerLogin\",\n batchCallService: \"batchCallService\",\n influxDbV2BaseService: \"influxDbV2BaseService\",\n systemInfoBundleService: \"systemInfoBundleService\",\n // Stores\n floorStore: \"floorStore\",\n settingsStore: \"settingsStore\",\n printerSocketStore: \"printerSocketStore\",\n testPrinterSocketStore: \"testPrinterSocketStore\",\n prusaLinkPollingAdapter: \"prusaLinkPollingAdapter\",\n octoPrintSockIoAdapter: \"octoPrintSockIoAdapter\",\n moonrakerWebsocketAdapter: \"moonrakerWebsocketAdapter\",\n // Caches\n printerCache: \"printerCache\",\n printerEventsCache: \"printerEventsCache\",\n printerThumbnailCache: \"printerThumbnailCache\",\n fileUploadTrackerCache: \"fileUploadTrackerCache\",\n // Tasks\n bootTask: \"bootTask\",\n softwareUpdateTask: \"softwareUpdateTask\",\n clientDistDownloadTask: \"clientDistDownloadTask\",\n socketIoTask: \"socketIoTask\",\n printerWebsocketTask: \"printerWebsocketTask\",\n printerWebsocketRestoreTask: \"printerWebsocketRestoreTask\",\n printJobAnalysisTask: \"printJobAnalysisTask\",\n} as const;\n"],"mappings":";AAAA,MAAa,WAAW;CACtB,eAAe;CAEf,eAAe;CACf,gBAAgB;CAChB,uBAAuB;CACvB,gBAAgB;CAEhB,YAAY;CACZ,iBAAiB;CACjB,eAAe;CACf,mBAAmB;CACnB,iBAAiB;CACjB,eAAe;CACf,eAAe;CACf,oBAAoB;CACpB,eAAe;CACf,eAAe;CACf,gBAAgB;CAChB,mBAAmB;CACnB,8BAA8B;CAC9B,sBAAsB;CACtB,iBAAiB;CACjB,mBAAmB;CACnB,oBAAoB;CACpB,qBAAqB;CACrB,4BAA4B;CAC5B,cAAc;CACd,aAAa;CACb,iBAAiB;CACjB,sBAAsB;CACtB,kBAAkB;CAClB,eAAe;CACf,gBAAgB;CAChB,qBAAqB;CACrB,gBAAgB;CAChB,kBAAkB;CAClB,aAAa;CACb,qBAAqB;CACrB,aAAa;CACb,iBAAiB;CACjB,mBAAmB;CACnB,YAAY;CACZ,aAAa;CACb,cAAc;CACd,cAAc;CACd,iBAAiB;CACjB,cAAc;CACd,iBAAiB;CACjB,UAAU;CACV,aAAa;CACb,iBAAiB;CACjB,kBAAkB;CAClB,qBAAqB;CACrB,eAAe;CACf,mBAAmB;CACnB,cAAc;CACd,kBAAkB;CAClB,uBAAuB;CACvB,yBAAyB;CAEzB,YAAY;CACZ,eAAe;CACf,oBAAoB;CACpB,wBAAwB;CACxB,yBAAyB;CACzB,wBAAwB;CACxB,2BAA2B;CAE3B,cAAc;CACd,oBAAoB;CACpB,uBAAuB;CACvB,wBAAwB;CAExB,UAAU;CACV,oBAAoB;CACpB,wBAAwB;CACxB,cAAc;CACd,sBAAsB;CACtB,6BAA6B;CAC7B,sBAAsB;CACvB"}
|
|
1
|
+
{"version":3,"file":"container.tokens.js","names":[],"sources":["../src/container.tokens.ts"],"sourcesContent":["export const DITokens = {\n cradleService: \"cradleService\",\n // Values\n serverVersion: \"serverVersion\",\n appDefaultRole: \"appDefaultRole\",\n appDefaultRoleNoLogin: \"appDefaultRoleNoLogin\",\n typeormService: \"typeormService\",\n // Instances\n serverHost: \"serverHost\",\n exceptionFilter: \"exceptionFilter\",\n loggerFactory: \"loggerFactory\",\n httpClientFactory: \"httpClientFactory\",\n socketIoGateway: \"socketIoGateway\",\n multerService: \"multerService\",\n configService: \"configService\",\n taskManagerService: \"taskManagerService\",\n toadScheduler: \"toadScheduler\",\n eventEmitter2: \"eventEmitter2\",\n printerService: \"printerService\",\n printerTagService: \"printerTagService\",\n printerMaintenanceLogService: \"printerMaintenanceLogService\",\n floorPositionService: \"floorPositionService\",\n printJobService: \"printJobService\",\n printQueueService: \"printQueueService\",\n fileStorageService: \"fileStorageService\",\n fileAnalysisService: \"fileAnalysisService\",\n printFileDownloaderService: \"printFileDownloaderService\",\n floorService: \"floorService\",\n yamlService: \"yamlService\",\n settingsService: \"settingsService\",\n serverReleaseService: \"serverReleaseService\",\n monsterPiService: \"monsterPiService\",\n githubService: \"githubService\",\n octokitService: \"octokitService\",\n clientBundleService: \"clientBundleService\",\n logDumpService: \"logDumpService\",\n userTokenService: \"userTokenService\",\n authService: \"authService\",\n refreshTokenService: \"refreshTokenService\",\n apiKeyService: \"apiKeyService\",\n userService: \"userService\",\n userRoleService: \"userRoleService\",\n permissionService: \"permissionService\",\n jwtService: \"jwtService\",\n roleService: \"roleService\",\n prusaLinkApi: \"prusaLinkApi\",\n octoprintApi: \"octoprintApi\",\n octoprintClient: \"octoprintClient\",\n moonrakerApi: \"moonrakerApi\",\n moonrakerClient: \"moonrakerClient\",\n bambuApi: \"bambuApi\",\n bambuClient: \"bambuClient\",\n bambuFtpAdapter: \"bambuFtpAdapter\",\n bambuMqttAdapter: \"bambuMqttAdapter\",\n cameraStreamService: \"cameraStreamService\",\n socketFactory: \"socketFactory\",\n printerApiFactory: \"printerApiFactory\",\n printerLogin: \"printerLogin\",\n batchCallService: \"batchCallService\",\n influxDbV2BaseService: \"influxDbV2BaseService\",\n systemInfoBundleService: \"systemInfoBundleService\",\n // Stores\n floorStore: \"floorStore\",\n settingsStore: \"settingsStore\",\n printerSocketStore: \"printerSocketStore\",\n testPrinterSocketStore: \"testPrinterSocketStore\",\n prusaLinkPollingAdapter: \"prusaLinkPollingAdapter\",\n octoPrintSockIoAdapter: \"octoPrintSockIoAdapter\",\n moonrakerWebsocketAdapter: \"moonrakerWebsocketAdapter\",\n // Caches\n printerCache: \"printerCache\",\n printerEventsCache: \"printerEventsCache\",\n printerThumbnailCache: \"printerThumbnailCache\",\n fileUploadTrackerCache: \"fileUploadTrackerCache\",\n // Tasks\n bootTask: \"bootTask\",\n softwareUpdateTask: \"softwareUpdateTask\",\n clientDistDownloadTask: \"clientDistDownloadTask\",\n socketIoTask: \"socketIoTask\",\n printerWebsocketTask: \"printerWebsocketTask\",\n printerWebsocketRestoreTask: \"printerWebsocketRestoreTask\",\n printJobAnalysisTask: \"printJobAnalysisTask\",\n} as const;\n"],"mappings":";AAAA,MAAa,WAAW;CACtB,eAAe;CAEf,eAAe;CACf,gBAAgB;CAChB,uBAAuB;CACvB,gBAAgB;CAEhB,YAAY;CACZ,iBAAiB;CACjB,eAAe;CACf,mBAAmB;CACnB,iBAAiB;CACjB,eAAe;CACf,eAAe;CACf,oBAAoB;CACpB,eAAe;CACf,eAAe;CACf,gBAAgB;CAChB,mBAAmB;CACnB,8BAA8B;CAC9B,sBAAsB;CACtB,iBAAiB;CACjB,mBAAmB;CACnB,oBAAoB;CACpB,qBAAqB;CACrB,4BAA4B;CAC5B,cAAc;CACd,aAAa;CACb,iBAAiB;CACjB,sBAAsB;CACtB,kBAAkB;CAClB,eAAe;CACf,gBAAgB;CAChB,qBAAqB;CACrB,gBAAgB;CAChB,kBAAkB;CAClB,aAAa;CACb,qBAAqB;CACrB,eAAe;CACf,aAAa;CACb,iBAAiB;CACjB,mBAAmB;CACnB,YAAY;CACZ,aAAa;CACb,cAAc;CACd,cAAc;CACd,iBAAiB;CACjB,cAAc;CACd,iBAAiB;CACjB,UAAU;CACV,aAAa;CACb,iBAAiB;CACjB,kBAAkB;CAClB,qBAAqB;CACrB,eAAe;CACf,mBAAmB;CACnB,cAAc;CACd,kBAAkB;CAClB,uBAAuB;CACvB,yBAAyB;CAEzB,YAAY;CACZ,eAAe;CACf,oBAAoB;CACpB,wBAAwB;CACxB,yBAAyB;CACzB,wBAAwB;CACxB,2BAA2B;CAE3B,cAAc;CACd,oBAAoB;CACpB,uBAAuB;CACvB,wBAAwB;CAExB,UAAU;CACV,oBAAoB;CACpB,wBAAwB;CACxB,cAAc;CACd,sBAAsB;CACtB,6BAA6B;CAC7B,sBAAsB;CACvB"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { __exportAll } from "../_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { __decorateMetadata } from "../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.js";
|
|
3
|
+
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.js";
|
|
4
|
+
import { AuthenticationError } from "../exceptions/runtime.exceptions.js";
|
|
5
|
+
import { validateInput, validateMiddleware } from "../handlers/validators.js";
|
|
6
|
+
import { AppConstants } from "../server.constants.js";
|
|
7
|
+
import { ROLES } from "../constants/authorization.constants.js";
|
|
8
|
+
import { authenticate, authorizeRoles } from "../middleware/authenticate.js";
|
|
9
|
+
import { demoUserNotAllowed } from "../middleware/demo.middleware.js";
|
|
10
|
+
import { apiKeyIdParamSchema, createApiKeySchema } from "./validation/api-key-controller.validation.js";
|
|
11
|
+
import { DELETE, GET, POST, before, route } from "awilix-express";
|
|
12
|
+
//#region src/controllers/api-key.controller.ts
|
|
13
|
+
var api_key_controller_exports = /* @__PURE__ */ __exportAll({ ApiKeyController: () => ApiKeyController });
|
|
14
|
+
let ApiKeyController = class ApiKeyController {
|
|
15
|
+
constructor(apiKeyService) {
|
|
16
|
+
this.apiKeyService = apiKeyService;
|
|
17
|
+
}
|
|
18
|
+
async list(_req, res) {
|
|
19
|
+
const keys = await this.apiKeyService.list();
|
|
20
|
+
res.send(keys);
|
|
21
|
+
}
|
|
22
|
+
async create(req, res) {
|
|
23
|
+
const createdByUserId = this.requireUserId(req);
|
|
24
|
+
const { label, roleIds } = await validateMiddleware(req, createApiKeySchema);
|
|
25
|
+
const created = await this.apiKeyService.create(createdByUserId, label, roleIds);
|
|
26
|
+
res.send(created);
|
|
27
|
+
}
|
|
28
|
+
async delete(req, res) {
|
|
29
|
+
const { id } = await validateInput(req.params, apiKeyIdParamSchema);
|
|
30
|
+
await this.apiKeyService.delete(id);
|
|
31
|
+
res.status(204).send();
|
|
32
|
+
}
|
|
33
|
+
requireUserId(req) {
|
|
34
|
+
const id = req.user?.id;
|
|
35
|
+
if (!id || id < 0) throw new AuthenticationError("Authenticated user is required");
|
|
36
|
+
return id;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
__decorate([
|
|
40
|
+
GET(),
|
|
41
|
+
route("/"),
|
|
42
|
+
__decorateMetadata("design:type", Function),
|
|
43
|
+
__decorateMetadata("design:paramtypes", [Object, Object]),
|
|
44
|
+
__decorateMetadata("design:returntype", Promise)
|
|
45
|
+
], ApiKeyController.prototype, "list", null);
|
|
46
|
+
__decorate([
|
|
47
|
+
POST(),
|
|
48
|
+
route("/"),
|
|
49
|
+
__decorateMetadata("design:type", Function),
|
|
50
|
+
__decorateMetadata("design:paramtypes", [Object, Object]),
|
|
51
|
+
__decorateMetadata("design:returntype", Promise)
|
|
52
|
+
], ApiKeyController.prototype, "create", null);
|
|
53
|
+
__decorate([
|
|
54
|
+
DELETE(),
|
|
55
|
+
route("/:id"),
|
|
56
|
+
__decorateMetadata("design:type", Function),
|
|
57
|
+
__decorateMetadata("design:paramtypes", [Object, Object]),
|
|
58
|
+
__decorateMetadata("design:returntype", Promise)
|
|
59
|
+
], ApiKeyController.prototype, "delete", null);
|
|
60
|
+
ApiKeyController = __decorate([
|
|
61
|
+
route(AppConstants.apiRoute + "/api-keys"),
|
|
62
|
+
before([
|
|
63
|
+
authenticate(),
|
|
64
|
+
authorizeRoles([ROLES.ADMIN]),
|
|
65
|
+
demoUserNotAllowed
|
|
66
|
+
]),
|
|
67
|
+
__decorateMetadata("design:paramtypes", [Object])
|
|
68
|
+
], ApiKeyController);
|
|
69
|
+
//#endregion
|
|
70
|
+
export { ApiKeyController, api_key_controller_exports };
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=api-key.controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.controller.js","names":[],"sources":["../../src/controllers/api-key.controller.ts"],"sourcesContent":["import { before, DELETE, GET, POST, route } from \"awilix-express\";\nimport { authenticate, authorizeRoles } from \"@/middleware/authenticate\";\nimport { demoUserNotAllowed } from \"@/middleware/demo.middleware\";\nimport { ROLES } from \"@/constants/authorization.constants\";\nimport { AppConstants } from \"@/server.constants\";\nimport { validateInput, validateMiddleware } from \"@/handlers/validators\";\nimport { apiKeyIdParamSchema, createApiKeySchema } from \"@/controllers/validation/api-key-controller.validation\";\nimport type { Request, Response } from \"express\";\nimport type { IApiKeyService } from \"@/services/interfaces/api-key.service.interface\";\nimport { AuthenticationError } from \"@/exceptions/runtime.exceptions\";\n\n/**\n * Admin-only API key management. Keys are m2m credentials with their own\n * role assignment (stored in the `api_key_role` join table); they are NOT\n * user impersonation tokens. `createdByUserId` is audit-only — it records\n * who minted the key, not whose permissions the key holds.\n */\n@route(AppConstants.apiRoute + \"/api-keys\")\n@before([authenticate(), authorizeRoles([ROLES.ADMIN]), demoUserNotAllowed])\nexport class ApiKeyController {\n constructor(private readonly apiKeyService: IApiKeyService) {}\n\n @GET()\n @route(\"/\")\n async list(_req: Request, res: Response) {\n const keys = await this.apiKeyService.list();\n res.send(keys);\n }\n\n @POST()\n @route(\"/\")\n async create(req: Request, res: Response) {\n const createdByUserId = this.requireUserId(req);\n const { label, roleIds } = await validateMiddleware(req, createApiKeySchema);\n const created = await this.apiKeyService.create(createdByUserId, label, roleIds);\n res.send(created);\n }\n\n @DELETE()\n @route(\"/:id\")\n async delete(req: Request, res: Response) {\n const { id } = await validateInput(req.params, apiKeyIdParamSchema);\n await this.apiKeyService.delete(id);\n res.status(204).send();\n }\n\n private requireUserId(req: Request): number {\n // Reachable when loginRequired=false (passport-anonymous falls through\n // without setting req.user); the @before authorizeRoles guard then\n // refuses, but defend the boundary anyway in case the chain is changed.\n const id = req.user?.id;\n if (!id || id < 0) {\n throw new AuthenticationError(\"Authenticated user is required\");\n }\n return id;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAmBO,IAAA,mBAAA,MAAM,iBAAiB;CAC5B,YAAY,eAAgD;AAA/B,OAAA,gBAAA;;CAE7B,MAEM,KAAK,MAAe,KAAe;EACvC,MAAM,OAAO,MAAM,KAAK,cAAc,MAAM;AAC5C,MAAI,KAAK,KAAK;;CAGhB,MAEM,OAAO,KAAc,KAAe;EACxC,MAAM,kBAAkB,KAAK,cAAc,IAAI;EAC/C,MAAM,EAAE,OAAO,YAAY,MAAM,mBAAmB,KAAK,mBAAmB;EAC5E,MAAM,UAAU,MAAM,KAAK,cAAc,OAAO,iBAAiB,OAAO,QAAQ;AAChF,MAAI,KAAK,QAAQ;;CAGnB,MAEM,OAAO,KAAc,KAAe;EACxC,MAAM,EAAE,OAAO,MAAM,cAAc,IAAI,QAAQ,oBAAoB;AACnE,QAAM,KAAK,cAAc,OAAO,GAAG;AACnC,MAAI,OAAO,IAAI,CAAC,MAAM;;CAGxB,cAAsB,KAAsB;EAI1C,MAAM,KAAK,IAAI,MAAM;AACrB,MAAI,CAAC,MAAM,KAAK,EACd,OAAM,IAAI,oBAAoB,iCAAiC;AAEjE,SAAO;;;;CAhCR,KAAK;CACL,MAAM,IAAI;;;;;;CAMV,MAAM;CACN,MAAM,IAAI;;;;;;CAQV,QAAQ;CACR,MAAM,OAAO;;;;;;CAtBf,MAAM,aAAa,WAAW,YAAY;CAC1C,OAAO;EAAC,cAAc;EAAE,eAAe,CAAC,MAAM,MAAM,CAAC;EAAE;EAAmB,CAAC"}
|
|
@@ -5,9 +5,9 @@ import { BadRequestException } from "../exceptions/runtime.exceptions.js";
|
|
|
5
5
|
import { validateMiddleware } from "../handlers/validators.js";
|
|
6
6
|
import { AppConstants } from "../server.constants.js";
|
|
7
7
|
import { SettingsStore } from "../state/settings.store.js";
|
|
8
|
-
import { refreshTokenSchema } from "./validation/auth-controller.validation.js";
|
|
9
8
|
import { authenticate } from "../middleware/authenticate.js";
|
|
10
9
|
import { demoUserNotAllowed } from "../middleware/demo.middleware.js";
|
|
10
|
+
import { refreshTokenSchema } from "./validation/auth-controller.validation.js";
|
|
11
11
|
import { registerUserSchema } from "./validation/user-controller.validation.js";
|
|
12
12
|
import { GET, POST, before, route } from "awilix-express";
|
|
13
13
|
//#region src/controllers/auth.controller.ts
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/controllers/validation/api-key-controller.validation.ts
|
|
3
|
+
const createApiKeySchema = z.object({
|
|
4
|
+
label: z.string().trim().min(1, "label is required").max(80, "label too long (max 80 chars)"),
|
|
5
|
+
roleIds: z.array(z.number().int().positive()).min(1, "at least one role is required")
|
|
6
|
+
});
|
|
7
|
+
const apiKeyIdParamSchema = z.object({ id: z.coerce.number().int().positive() });
|
|
8
|
+
//#endregion
|
|
9
|
+
export { apiKeyIdParamSchema, createApiKeySchema };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=api-key-controller.validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key-controller.validation.js","names":[],"sources":["../../../src/controllers/validation/api-key-controller.validation.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const createApiKeySchema = z.object({\n label: z.string().trim().min(1, \"label is required\").max(80, \"label too long (max 80 chars)\"),\n roleIds: z.array(z.number().int().positive()).min(1, \"at least one role is required\"),\n});\n\nexport const apiKeyIdParamSchema = z.object({\n id: z.coerce.number().int().positive(),\n});\n"],"mappings":";;AAEA,MAAa,qBAAqB,EAAE,OAAO;CACzC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,GAAG,oBAAoB,CAAC,IAAI,IAAI,gCAAgC;CAC7F,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,GAAG,gCAAgC;CACtF,CAAC;AAEF,MAAa,sBAAsB,EAAE,OAAO,EAC1C,IAAI,EAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,UAAU,EACvC,CAAC"}
|
package/dist/data-source.js
CHANGED
|
@@ -12,6 +12,7 @@ import { CameraStream } from "./entities/camera-stream.entity.js";
|
|
|
12
12
|
import { FloorPosition } from "./entities/floor-position.entity.js";
|
|
13
13
|
import { Floor } from "./entities/floor.entity.js";
|
|
14
14
|
import { Settings } from "./entities/settings.entity.js";
|
|
15
|
+
import { ApiKey } from "./entities/api-key.entity.js";
|
|
15
16
|
import "./entities/index.js";
|
|
16
17
|
import { InitSqlite1706829146617 } from "./migrations/1706829146617-InitSqlite.js";
|
|
17
18
|
import { PrinterGroup1707494762198 } from "./migrations/1707494762198-PrinterGroup.js";
|
|
@@ -29,6 +30,7 @@ import { ChangeFloorNonUniqueOrder1767370191762 } from "./migrations/17673701917
|
|
|
29
30
|
import { RenameGroupToTag1767432108916 } from "./migrations/1767432108916-RenameGroupToTag.js";
|
|
30
31
|
import { AddPrintJob1767451444137 } from "./migrations/1767451444137-AddPrintJob.js";
|
|
31
32
|
import { AddPrinterMaintenanceLog1767909428129 } from "./migrations/1767909428129-AddPrinterMaintenanceLog.js";
|
|
33
|
+
import { AddApiKey1778446203015 } from "./migrations/1778446203015-AddApiKey.js";
|
|
32
34
|
import { DataSource } from "typeorm";
|
|
33
35
|
import "reflect-metadata";
|
|
34
36
|
const AppDataSource = new DataSource({
|
|
@@ -49,7 +51,8 @@ const AppDataSource = new DataSource({
|
|
|
49
51
|
Tag,
|
|
50
52
|
PrinterTag,
|
|
51
53
|
PrintJob,
|
|
52
|
-
PrinterMaintenanceLog
|
|
54
|
+
PrinterMaintenanceLog,
|
|
55
|
+
ApiKey
|
|
53
56
|
],
|
|
54
57
|
migrations: [
|
|
55
58
|
InitSqlite1706829146617,
|
|
@@ -67,7 +70,8 @@ const AppDataSource = new DataSource({
|
|
|
67
70
|
ChangeFloorNonUniqueOrder1767370191762,
|
|
68
71
|
RenameGroupToTag1767432108916,
|
|
69
72
|
AddPrintJob1767451444137,
|
|
70
|
-
AddPrinterMaintenanceLog1767909428129
|
|
73
|
+
AddPrinterMaintenanceLog1767909428129,
|
|
74
|
+
AddApiKey1778446203015
|
|
71
75
|
],
|
|
72
76
|
subscribers: []
|
|
73
77
|
});
|
package/dist/data-source.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-source.js","names":[],"sources":["../src/data-source.ts"],"sourcesContent":["import \"reflect-metadata\";\nimport { DataSource } from \"typeorm\";\nimport { getDatabaseFilePath } from \"@/utils/fs.utils\";\nimport { Floor } from \"@/entities/floor.entity\";\nimport { FloorPosition } from \"@/entities/floor-position.entity\";\nimport { Printer } from \"@/entities/printer.entity\";\nimport { Settings } from \"@/entities/settings.entity\";\nimport { PrintJob, RefreshToken, User, PrinterMaintenanceLog } from \"@/entities\";\nimport { CameraStream } from \"@/entities/camera-stream.entity\";\nimport { Role } from \"@/entities/role.entity\";\nimport { UserRole } from \"@/entities/user-role.entity\";\nimport { InitSqlite1706829146617 } from \"@/migrations/1706829146617-InitSqlite\";\nimport { PrinterTag } from \"@/entities/printer-tag.entity\";\nimport { Tag } from \"@/entities/tag.entity\";\nimport { PrinterGroup1707494762198 } from \"@/migrations/1707494762198-PrinterGroup\";\nimport { ChangePrintCompletionDeletePrinterCascade1708465930665 } from \"@/migrations/1708465930665-ChangePrintCompletionDeletePrinterCascade\";\nimport { ChangeRoleNameUnique1713300747465 } from \"@/migrations/1713300747465-ChangeRoleNameUnique\";\nimport { RemovePrinterFile1720338804844 } from \"@/migrations/1720338804844-RemovePrinterFile\";\nimport { AddPrinterType1713897879622 } from \"@/migrations/1713897879622-AddPrinterType\";\nimport { AddPrinterUsernamePassword1745141688926 } from \"@/migrations/1745141688926-AddPrinterUsernamePassword\";\nimport { DropPermissions1766576698569 } from \"@/migrations/1766576698569-DropPermissions\";\nimport { ChangeCameraPrinterOnDeleteSetNull1767278216516 } from \"@/migrations/1767278216516-ChangeCameraPrinterOnDeleteSetNull\";\nimport { DropCustomGcode1767279607392 } from \"@/migrations/1767279607392-DropCustomGcode\";\nimport { DropPrintCompletions1767291804417 } from \"@/migrations/1767291804417-DropPrintCompletions\";\nimport { DropSettingsFileClean1767352862576 } from \"@/migrations/1767352862576-DropSettingsFileClean\";\nimport { ChangeFloorNonUniqueOrder1767370191762 } from \"@/migrations/1767370191762-ChangeFloorNonUniqueOrder\";\nimport { RenameGroupToTag1767432108916 } from \"@/migrations/1767432108916-RenameGroupToTag\";\nimport { AddPrintJob1767451444137 } from \"@/migrations/1767451444137-AddPrintJob\";\nimport { AddPrinterMaintenanceLog1767909428129 } from \"@/migrations/1767909428129-AddPrinterMaintenanceLog\";\n\nconst databaseFilePath = getDatabaseFilePath();\n\nexport const AppDataSource = new DataSource({\n type: \"better-sqlite3\",\n database: databaseFilePath,\n synchronize: false,\n logging: false,\n entities: [\n Floor,\n FloorPosition,\n Printer,\n Settings,\n User,\n CameraStream,\n Role,\n RefreshToken,\n UserRole,\n Tag,\n PrinterTag,\n PrintJob,\n PrinterMaintenanceLog,\n ],\n migrations: [\n InitSqlite1706829146617,\n PrinterGroup1707494762198,\n ChangePrintCompletionDeletePrinterCascade1708465930665,\n ChangeRoleNameUnique1713300747465,\n RemovePrinterFile1720338804844,\n AddPrinterType1713897879622,\n AddPrinterUsernamePassword1745141688926,\n DropPermissions1766576698569,\n ChangeCameraPrinterOnDeleteSetNull1767278216516,\n DropCustomGcode1767279607392,\n DropPrintCompletions1767291804417,\n DropSettingsFileClean1767352862576,\n ChangeFloorNonUniqueOrder1767370191762,\n RenameGroupToTag1767432108916,\n AddPrintJob1767451444137,\n AddPrinterMaintenanceLog1767909428129,\n ],\n subscribers: [],\n});\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"data-source.js","names":[],"sources":["../src/data-source.ts"],"sourcesContent":["import \"reflect-metadata\";\nimport { DataSource } from \"typeorm\";\nimport { getDatabaseFilePath } from \"@/utils/fs.utils\";\nimport { Floor } from \"@/entities/floor.entity\";\nimport { FloorPosition } from \"@/entities/floor-position.entity\";\nimport { Printer } from \"@/entities/printer.entity\";\nimport { Settings } from \"@/entities/settings.entity\";\nimport { PrintJob, RefreshToken, User, PrinterMaintenanceLog, ApiKey } from \"@/entities\";\nimport { CameraStream } from \"@/entities/camera-stream.entity\";\nimport { Role } from \"@/entities/role.entity\";\nimport { UserRole } from \"@/entities/user-role.entity\";\nimport { InitSqlite1706829146617 } from \"@/migrations/1706829146617-InitSqlite\";\nimport { PrinterTag } from \"@/entities/printer-tag.entity\";\nimport { Tag } from \"@/entities/tag.entity\";\nimport { PrinterGroup1707494762198 } from \"@/migrations/1707494762198-PrinterGroup\";\nimport { ChangePrintCompletionDeletePrinterCascade1708465930665 } from \"@/migrations/1708465930665-ChangePrintCompletionDeletePrinterCascade\";\nimport { ChangeRoleNameUnique1713300747465 } from \"@/migrations/1713300747465-ChangeRoleNameUnique\";\nimport { RemovePrinterFile1720338804844 } from \"@/migrations/1720338804844-RemovePrinterFile\";\nimport { AddPrinterType1713897879622 } from \"@/migrations/1713897879622-AddPrinterType\";\nimport { AddPrinterUsernamePassword1745141688926 } from \"@/migrations/1745141688926-AddPrinterUsernamePassword\";\nimport { DropPermissions1766576698569 } from \"@/migrations/1766576698569-DropPermissions\";\nimport { ChangeCameraPrinterOnDeleteSetNull1767278216516 } from \"@/migrations/1767278216516-ChangeCameraPrinterOnDeleteSetNull\";\nimport { DropCustomGcode1767279607392 } from \"@/migrations/1767279607392-DropCustomGcode\";\nimport { DropPrintCompletions1767291804417 } from \"@/migrations/1767291804417-DropPrintCompletions\";\nimport { DropSettingsFileClean1767352862576 } from \"@/migrations/1767352862576-DropSettingsFileClean\";\nimport { ChangeFloorNonUniqueOrder1767370191762 } from \"@/migrations/1767370191762-ChangeFloorNonUniqueOrder\";\nimport { RenameGroupToTag1767432108916 } from \"@/migrations/1767432108916-RenameGroupToTag\";\nimport { AddPrintJob1767451444137 } from \"@/migrations/1767451444137-AddPrintJob\";\nimport { AddPrinterMaintenanceLog1767909428129 } from \"@/migrations/1767909428129-AddPrinterMaintenanceLog\";\nimport { AddApiKey1778446203015 } from \"@/migrations/1778446203015-AddApiKey\";\n\nconst databaseFilePath = getDatabaseFilePath();\n\nexport const AppDataSource = new DataSource({\n type: \"better-sqlite3\",\n database: databaseFilePath,\n synchronize: false,\n logging: false,\n entities: [\n Floor,\n FloorPosition,\n Printer,\n Settings,\n User,\n CameraStream,\n Role,\n RefreshToken,\n UserRole,\n Tag,\n PrinterTag,\n PrintJob,\n PrinterMaintenanceLog,\n ApiKey,\n ],\n migrations: [\n InitSqlite1706829146617,\n PrinterGroup1707494762198,\n ChangePrintCompletionDeletePrinterCascade1708465930665,\n ChangeRoleNameUnique1713300747465,\n RemovePrinterFile1720338804844,\n AddPrinterType1713897879622,\n AddPrinterUsernamePassword1745141688926,\n DropPermissions1766576698569,\n ChangeCameraPrinterOnDeleteSetNull1767278216516,\n DropCustomGcode1767279607392,\n DropPrintCompletions1767291804417,\n DropSettingsFileClean1767352862576,\n ChangeFloorNonUniqueOrder1767370191762,\n RenameGroupToTag1767432108916,\n AddPrintJob1767451444137,\n AddPrinterMaintenanceLog1767909428129,\n AddApiKey1778446203015,\n ],\n subscribers: [],\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAa,gBAAgB,IAAI,WAAW;CAC1C,MAAM;CACN,UAJuB,qBAIb;CACV,aAAa;CACb,SAAS;CACT,UAAU;EACR;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD;CACD,YAAY;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD;CACD,aAAa,EAAE;CAChB,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { __decorateMetadata } from "../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.js";
|
|
2
|
+
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.js";
|
|
3
|
+
import { Role } from "./role.entity.js";
|
|
4
|
+
import { User } from "./user.entity.js";
|
|
5
|
+
import { Column, CreateDateColumn, Entity, Index, JoinColumn, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
|
|
6
|
+
//#region src/entities/api-key.entity.ts
|
|
7
|
+
var _ref;
|
|
8
|
+
let ApiKey = class ApiKey {
|
|
9
|
+
id;
|
|
10
|
+
createdByUser;
|
|
11
|
+
createdByUserId;
|
|
12
|
+
label;
|
|
13
|
+
prefix;
|
|
14
|
+
hashedSecret;
|
|
15
|
+
createdAt;
|
|
16
|
+
lastUsedAt;
|
|
17
|
+
roles;
|
|
18
|
+
};
|
|
19
|
+
__decorate([PrimaryGeneratedColumn(), __decorateMetadata("design:type", Number)], ApiKey.prototype, "id", void 0);
|
|
20
|
+
__decorate([
|
|
21
|
+
ManyToOne(() => User, {
|
|
22
|
+
nullable: false,
|
|
23
|
+
onDelete: "CASCADE"
|
|
24
|
+
}),
|
|
25
|
+
JoinColumn({ name: "createdByUserId" }),
|
|
26
|
+
__decorateMetadata("design:type", Object)
|
|
27
|
+
], ApiKey.prototype, "createdByUser", void 0);
|
|
28
|
+
__decorate([Column(), __decorateMetadata("design:type", Number)], ApiKey.prototype, "createdByUserId", void 0);
|
|
29
|
+
__decorate([Column(), __decorateMetadata("design:type", String)], ApiKey.prototype, "label", void 0);
|
|
30
|
+
__decorate([
|
|
31
|
+
Index(),
|
|
32
|
+
Column({ unique: true }),
|
|
33
|
+
__decorateMetadata("design:type", String)
|
|
34
|
+
], ApiKey.prototype, "prefix", void 0);
|
|
35
|
+
__decorate([Column(), __decorateMetadata("design:type", String)], ApiKey.prototype, "hashedSecret", void 0);
|
|
36
|
+
__decorate([CreateDateColumn(), __decorateMetadata("design:type", typeof (_ref = typeof Date !== "undefined" && Date) === "function" ? _ref : Object)], ApiKey.prototype, "createdAt", void 0);
|
|
37
|
+
__decorate([Column({
|
|
38
|
+
type: "datetime",
|
|
39
|
+
nullable: true
|
|
40
|
+
}), __decorateMetadata("design:type", Object)], ApiKey.prototype, "lastUsedAt", void 0);
|
|
41
|
+
__decorate([
|
|
42
|
+
ManyToMany(() => Role, { eager: true }),
|
|
43
|
+
JoinTable({
|
|
44
|
+
name: "api_key_role",
|
|
45
|
+
joinColumn: {
|
|
46
|
+
name: "apiKeyId",
|
|
47
|
+
referencedColumnName: "id"
|
|
48
|
+
},
|
|
49
|
+
inverseJoinColumn: {
|
|
50
|
+
name: "roleId",
|
|
51
|
+
referencedColumnName: "id"
|
|
52
|
+
}
|
|
53
|
+
}),
|
|
54
|
+
__decorateMetadata("design:type", Array)
|
|
55
|
+
], ApiKey.prototype, "roles", void 0);
|
|
56
|
+
ApiKey = __decorate([Entity()], ApiKey);
|
|
57
|
+
//#endregion
|
|
58
|
+
export { ApiKey };
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=api-key.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.entity.js","names":[],"sources":["../../src/entities/api-key.entity.ts"],"sourcesContent":["import {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n JoinColumn,\n JoinTable,\n ManyToMany,\n ManyToOne,\n PrimaryGeneratedColumn,\n type Relation,\n} from \"typeorm\";\nimport { User } from \"@/entities/user.entity\";\nimport { Role } from \"@/entities/role.entity\";\n\n/**\n * Long-lived bearer credential for programmatic API access.\n *\n * Token format: `fdmm_api_<base64url>` (high-entropy CSPRNG). The `prefix`\n * is the indexable lookup column; `hashedSecret` is sha256 of the full\n * token, verified with timingSafeEqual.\n *\n * Permissions are derived from `roles` (via the `api_key_role` join table),\n * NOT from the bound user's roles. `createdByUserId` is audit-only — it\n * records who minted the key and does not drive authorisation.\n */\n@Entity()\nexport class ApiKey {\n @PrimaryGeneratedColumn()\n id: number;\n\n @ManyToOne(() => User, { nullable: false, onDelete: \"CASCADE\" })\n @JoinColumn({ name: \"createdByUserId\" })\n createdByUser: Relation<User>;\n\n @Column()\n createdByUserId: number;\n\n @Column()\n label: string;\n\n @Index()\n @Column({ unique: true })\n prefix: string;\n\n @Column()\n hashedSecret: string;\n\n @CreateDateColumn()\n createdAt: Date;\n\n @Column({ type: \"datetime\", nullable: true })\n lastUsedAt: Date | null;\n\n @ManyToMany(() => Role, { eager: true })\n @JoinTable({\n name: \"api_key_role\",\n joinColumn: { name: \"apiKeyId\", referencedColumnName: \"id\" },\n inverseJoinColumn: { name: \"roleId\", referencedColumnName: \"id\" },\n })\n roles: Relation<Role>[];\n}\n"],"mappings":";;;;;;;AA2BO,IAAA,SAAA,MAAM,OAAO;CAClB;CAGA;CAIA;CAGA;CAGA;CAIA;CAGA;CAGA;CAGA;;YA1BC,wBAAwB,EAAA,mBAAA,eAAA,OAAA,CAAA,EAAA,OAAA,WAAA,MAAA,KAAA,EAAA;;CAGxB,gBAAgB,MAAM;EAAE,UAAU;EAAO,UAAU;EAAW,CAAC;CAC/D,WAAW,EAAE,MAAM,mBAAmB,CAAC;;;YAGvC,QAAQ,EAAA,mBAAA,eAAA,OAAA,CAAA,EAAA,OAAA,WAAA,mBAAA,KAAA,EAAA;YAGR,QAAQ,EAAA,mBAAA,eAAA,OAAA,CAAA,EAAA,OAAA,WAAA,SAAA,KAAA,EAAA;;CAGR,OAAO;CACP,OAAO,EAAE,QAAQ,MAAM,CAAC;;;YAGxB,QAAQ,EAAA,mBAAA,eAAA,OAAA,CAAA,EAAA,OAAA,WAAA,gBAAA,KAAA,EAAA;YAGR,kBAAkB,EAAA,mBAAA,eAAA,QAAA,OAAA,OAAA,SAAA,eAAA,UAAA,aAAA,OAAA,OAAA,CAAA,EAAA,OAAA,WAAA,aAAA,KAAA,EAAA;YAGlB,OAAO;CAAE,MAAM;CAAY,UAAU;CAAM,CAAC,EAAA,mBAAA,eAAA,OAAA,CAAA,EAAA,OAAA,WAAA,cAAA,KAAA,EAAA;;CAG5C,iBAAiB,MAAM,EAAE,OAAO,MAAM,CAAC;CACvC,UAAU;EACT,MAAM;EACN,YAAY;GAAE,MAAM;GAAY,sBAAsB;GAAM;EAC5D,mBAAmB;GAAE,MAAM;GAAU,sBAAsB;GAAM;EAClE,CAAC;;;qBAjCH,QAAQ,CAAA,EAAA,OAAA"}
|
package/dist/entities/index.js
CHANGED
|
@@ -11,4 +11,5 @@ import { CameraStream } from "./camera-stream.entity.js";
|
|
|
11
11
|
import { FloorPosition } from "./floor-position.entity.js";
|
|
12
12
|
import { Floor } from "./floor.entity.js";
|
|
13
13
|
import { Settings } from "./settings.entity.js";
|
|
14
|
-
|
|
14
|
+
import { ApiKey } from "./api-key.entity.js";
|
|
15
|
+
export { ApiKey, CameraStream, Floor, FloorPosition, PrintJob, Printer, PrinterMaintenanceLog, PrinterTag, RefreshToken, Role, Settings, Tag, User, UserRole };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Strategy } from "passport";
|
|
2
|
+
//#region src/middleware/api-key.strategy.ts
|
|
3
|
+
/**
|
|
4
|
+
* Passport strategy for API-key bearer auth. Slotted between JWT and Anonymous
|
|
5
|
+
* so a request with no auth header still falls through to anonymous.
|
|
6
|
+
*
|
|
7
|
+
* Important: we do NOT look up the bound user. The api_key_role join is the
|
|
8
|
+
* sole permission source for the request — keys are self-contained credentials,
|
|
9
|
+
* not user impersonation. `req.user.isApiKey === true` and `req.user.id = -1`
|
|
10
|
+
* are how downstream audit/branching code can detect an api-key principal.
|
|
11
|
+
*/
|
|
12
|
+
var ApiKeyStrategy = class extends Strategy {
|
|
13
|
+
name = "api-key";
|
|
14
|
+
constructor(apiKeyService) {
|
|
15
|
+
super();
|
|
16
|
+
this.apiKeyService = apiKeyService;
|
|
17
|
+
}
|
|
18
|
+
async authenticate(req, _options) {
|
|
19
|
+
const header = req.headers.authorization;
|
|
20
|
+
const token = header?.startsWith("Bearer ") ? header.slice(7) : void 0;
|
|
21
|
+
if (!token || !this.apiKeyService.looksLikeApiKey(token)) return this.pass();
|
|
22
|
+
try {
|
|
23
|
+
const apiKey = await this.apiKeyService.verify(token);
|
|
24
|
+
if (!apiKey) return this.fail({ message: "Invalid API key" }, 401);
|
|
25
|
+
const principal = {
|
|
26
|
+
id: -1,
|
|
27
|
+
username: `api-key:${apiKey.id}`,
|
|
28
|
+
isDemoUser: false,
|
|
29
|
+
isRootUser: false,
|
|
30
|
+
isVerified: true,
|
|
31
|
+
needsPasswordChange: false,
|
|
32
|
+
createdAt: apiKey.createdAt,
|
|
33
|
+
roles: (apiKey.roles ?? []).map((r) => r.name),
|
|
34
|
+
isApiKey: true
|
|
35
|
+
};
|
|
36
|
+
return this.success(principal);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
return this.error(err instanceof Error ? err : new Error(String(err)));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
//#endregion
|
|
43
|
+
export { ApiKeyStrategy };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=api-key.strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.strategy.js","names":[],"sources":["../../src/middleware/api-key.strategy.ts"],"sourcesContent":["import { Strategy } from \"passport\";\nimport type { Request } from \"express\";\nimport type { IApiKeyService } from \"@/services/interfaces/api-key.service.interface\";\nimport type { UserDto } from \"@/services/interfaces/user.dto\";\nimport type { RoleName } from \"@/constants/authorization.constants\";\n\n/**\n * Passport strategy for API-key bearer auth. Slotted between JWT and Anonymous\n * so a request with no auth header still falls through to anonymous.\n *\n * Important: we do NOT look up the bound user. The api_key_role join is the\n * sole permission source for the request — keys are self-contained credentials,\n * not user impersonation. `req.user.isApiKey === true` and `req.user.id = -1`\n * are how downstream audit/branching code can detect an api-key principal.\n */\nexport class ApiKeyStrategy extends Strategy {\n name = \"api-key\";\n\n constructor(private readonly apiKeyService: IApiKeyService) {\n super();\n }\n\n async authenticate(req: Request, _options?: any): Promise<void> {\n const header = req.headers.authorization;\n const token = header?.startsWith(\"Bearer \") ? header.slice(\"Bearer \".length) : undefined;\n if (!token || !this.apiKeyService.looksLikeApiKey(token)) {\n return this.pass();\n }\n\n try {\n const apiKey = await this.apiKeyService.verify(token);\n if (!apiKey) {\n return this.fail({ message: \"Invalid API key\" }, 401);\n }\n const principal: UserDto = {\n id: -1,\n username: `api-key:${apiKey.id}`,\n isDemoUser: false,\n isRootUser: false,\n isVerified: true,\n needsPasswordChange: false,\n createdAt: apiKey.createdAt,\n roles: (apiKey.roles ?? []).map((r) => r.name as RoleName),\n isApiKey: true,\n };\n return this.success(principal);\n } catch (err) {\n return this.error(err instanceof Error ? err : new Error(String(err)));\n }\n }\n}\n"],"mappings":";;;;;;;;;;;AAeA,IAAa,iBAAb,cAAoC,SAAS;CAC3C,OAAO;CAEP,YAAY,eAAgD;AAC1D,SAAO;AADoB,OAAA,gBAAA;;CAI7B,MAAM,aAAa,KAAc,UAA+B;EAC9D,MAAM,SAAS,IAAI,QAAQ;EAC3B,MAAM,QAAQ,QAAQ,WAAW,UAAU,GAAG,OAAO,MAAM,EAAiB,GAAG,KAAA;AAC/E,MAAI,CAAC,SAAS,CAAC,KAAK,cAAc,gBAAgB,MAAM,CACtD,QAAO,KAAK,MAAM;AAGpB,MAAI;GACF,MAAM,SAAS,MAAM,KAAK,cAAc,OAAO,MAAM;AACrD,OAAI,CAAC,OACH,QAAO,KAAK,KAAK,EAAE,SAAS,mBAAmB,EAAE,IAAI;GAEvD,MAAM,YAAqB;IACzB,IAAI;IACJ,UAAU,WAAW,OAAO;IAC5B,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,qBAAqB;IACrB,WAAW,OAAO;IAClB,QAAQ,OAAO,SAAS,EAAE,EAAE,KAAK,MAAM,EAAE,KAAiB;IAC1D,UAAU;IACX;AACD,UAAO,KAAK,QAAQ,UAAU;WACvB,KAAK;AACZ,UAAO,KAAK,MAAM,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,CAAC"}
|
|
@@ -2,6 +2,7 @@ import { DITokens } from "../container.tokens.js";
|
|
|
2
2
|
import { AuthenticationError } from "../exceptions/runtime.exceptions.js";
|
|
3
3
|
import { AppConstants } from "../server.constants.js";
|
|
4
4
|
import { AUTH_ERROR_REASON } from "../constants/authorization.constants.js";
|
|
5
|
+
import { ApiKeyStrategy } from "./api-key.strategy.js";
|
|
5
6
|
import { ExtractJwt, Strategy } from "passport-jwt";
|
|
6
7
|
import { Strategy as Strategy$1 } from "passport-anonymous";
|
|
7
8
|
//#region src/middleware/passport.ts
|
|
@@ -32,10 +33,12 @@ function initializePassportStrategies(passport, container) {
|
|
|
32
33
|
const settingsStore = container.resolve(DITokens.settingsStore);
|
|
33
34
|
const configService = container.resolve(DITokens.configService);
|
|
34
35
|
const userService = container.resolve(DITokens.userService);
|
|
36
|
+
const apiKeyService = container.resolve(DITokens.apiKeyService);
|
|
35
37
|
const opts = getPassportJwtOptions(settingsStore, configService, ExtractJwt.fromAuthHeaderAsBearerToken());
|
|
36
38
|
passport.use(new Strategy(opts, function(jwt_payload, done) {
|
|
37
39
|
verifyUserCallback(userService)(jwt_payload, done);
|
|
38
40
|
}));
|
|
41
|
+
passport.use(new ApiKeyStrategy(apiKeyService));
|
|
39
42
|
passport.use(new Strategy$1());
|
|
40
43
|
return passport;
|
|
41
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passport.js","names":["JwtStrategy","AnonymousStrategy"],"sources":["../../src/middleware/passport.ts"],"sourcesContent":["import {\n ExtractJwt,\n JwtFromRequestFunction,\n Strategy as JwtStrategy,\n StrategyOptionsWithoutRequest,\n VerifiedCallback,\n} from \"passport-jwt\";\nimport { Strategy as AnonymousStrategy } from \"passport-anonymous\";\nimport { DITokens } from \"@/container.tokens\";\nimport { AppConstants } from \"@/server.constants\";\nimport { AwilixContainer } from \"awilix\";\nimport { PassportStatic } from \"passport\";\nimport { SettingsStore } from \"@/state/settings.store\";\nimport { ConfigService } from \"@/services/core/config.service\";\nimport type { IUserService } from \"@/services/interfaces/user-service.interface\";\nimport { Socket } from \"socket.io\";\nimport { AuthenticationError } from \"@/exceptions/runtime.exceptions\";\nimport { AUTH_ERROR_REASON } from \"@/constants/authorization.constants\";\nimport { User } from \"@/entities\";\n\nexport type JwtFromSocketFunction = (socket: Socket) => string | null;\n\nexport function getPassportJwtOptions(\n settingsStore: SettingsStore,\n configService: ConfigService,\n jwtFromRequest: JwtFromRequestFunction | JwtFromSocketFunction = ExtractJwt.fromAuthHeaderAsBearerToken(),\n): StrategyOptionsWithoutRequest {\n return {\n jwtFromRequest: jwtFromRequest,\n secretOrKeyProvider: async (_req, _token: string, done) => {\n const { jwtSecret } = await settingsStore.getCredentialSettings();\n return done(null, jwtSecret);\n },\n audience: configService.get(AppConstants.OVERRIDE_JWT_AUDIENCE, AppConstants.DEFAULT_JWT_AUDIENCE),\n issuer: configService.get(AppConstants.OVERRIDE_JWT_ISSUER, AppConstants.DEFAULT_JWT_ISSUER),\n };\n}\n\nexport function verifyUserCallback(userService: IUserService) {\n return function (jwt_payload: any, done: VerifiedCallback) {\n userService\n .getUser(jwt_payload.userId)\n .then((user: User) => {\n if (user?.isVerified && !user.needsPasswordChange) {\n return done(null, userService.toDto(user));\n }\n if (user?.needsPasswordChange) {\n return done(\n new AuthenticationError(\"Password change required\", AUTH_ERROR_REASON.PasswordChangeRequired),\n false,\n );\n }\n if (!user?.isVerified) {\n return done(new AuthenticationError(\"User not verified\", AUTH_ERROR_REASON.AccountNotVerified), false);\n }\n\n return done(null, false);\n })\n .catch((err) => {\n if (err) {\n return done(err, false);\n }\n });\n };\n}\n\nexport function initializePassportStrategies(passport: PassportStatic, container: AwilixContainer): PassportStatic {\n const settingsStore = container.resolve<SettingsStore>(DITokens.settingsStore);\n const configService = container.resolve<ConfigService>(DITokens.configService);\n const userService = container.resolve<IUserService>(DITokens.userService);\n\n const opts = getPassportJwtOptions(settingsStore, configService, ExtractJwt.fromAuthHeaderAsBearerToken());\n\n passport.use(\n new JwtStrategy(opts, function (jwt_payload: any, done: VerifiedCallback) {\n verifyUserCallback(userService)(jwt_payload, done);\n }),\n );\n passport.use(new AnonymousStrategy());\n return passport;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"passport.js","names":["JwtStrategy","AnonymousStrategy"],"sources":["../../src/middleware/passport.ts"],"sourcesContent":["import {\n ExtractJwt,\n JwtFromRequestFunction,\n Strategy as JwtStrategy,\n StrategyOptionsWithoutRequest,\n VerifiedCallback,\n} from \"passport-jwt\";\nimport { Strategy as AnonymousStrategy } from \"passport-anonymous\";\nimport { DITokens } from \"@/container.tokens\";\nimport { AppConstants } from \"@/server.constants\";\nimport { AwilixContainer } from \"awilix\";\nimport { PassportStatic } from \"passport\";\nimport { SettingsStore } from \"@/state/settings.store\";\nimport { ConfigService } from \"@/services/core/config.service\";\nimport type { IUserService } from \"@/services/interfaces/user-service.interface\";\nimport type { IApiKeyService } from \"@/services/interfaces/api-key.service.interface\";\nimport { Socket } from \"socket.io\";\nimport { AuthenticationError } from \"@/exceptions/runtime.exceptions\";\nimport { AUTH_ERROR_REASON } from \"@/constants/authorization.constants\";\nimport { User } from \"@/entities\";\nimport { ApiKeyStrategy } from \"@/middleware/api-key.strategy\";\n\nexport type JwtFromSocketFunction = (socket: Socket) => string | null;\n\nexport function getPassportJwtOptions(\n settingsStore: SettingsStore,\n configService: ConfigService,\n jwtFromRequest: JwtFromRequestFunction | JwtFromSocketFunction = ExtractJwt.fromAuthHeaderAsBearerToken(),\n): StrategyOptionsWithoutRequest {\n return {\n jwtFromRequest: jwtFromRequest,\n secretOrKeyProvider: async (_req, _token: string, done) => {\n const { jwtSecret } = await settingsStore.getCredentialSettings();\n return done(null, jwtSecret);\n },\n audience: configService.get(AppConstants.OVERRIDE_JWT_AUDIENCE, AppConstants.DEFAULT_JWT_AUDIENCE),\n issuer: configService.get(AppConstants.OVERRIDE_JWT_ISSUER, AppConstants.DEFAULT_JWT_ISSUER),\n };\n}\n\nexport function verifyUserCallback(userService: IUserService) {\n return function (jwt_payload: any, done: VerifiedCallback) {\n userService\n .getUser(jwt_payload.userId)\n .then((user: User) => {\n if (user?.isVerified && !user.needsPasswordChange) {\n return done(null, userService.toDto(user));\n }\n if (user?.needsPasswordChange) {\n return done(\n new AuthenticationError(\"Password change required\", AUTH_ERROR_REASON.PasswordChangeRequired),\n false,\n );\n }\n if (!user?.isVerified) {\n return done(new AuthenticationError(\"User not verified\", AUTH_ERROR_REASON.AccountNotVerified), false);\n }\n\n return done(null, false);\n })\n .catch((err) => {\n if (err) {\n return done(err, false);\n }\n });\n };\n}\n\nexport function initializePassportStrategies(passport: PassportStatic, container: AwilixContainer): PassportStatic {\n const settingsStore = container.resolve<SettingsStore>(DITokens.settingsStore);\n const configService = container.resolve<ConfigService>(DITokens.configService);\n const userService = container.resolve<IUserService>(DITokens.userService);\n const apiKeyService = container.resolve<IApiKeyService>(DITokens.apiKeyService);\n\n const opts = getPassportJwtOptions(settingsStore, configService, ExtractJwt.fromAuthHeaderAsBearerToken());\n\n passport.use(\n new JwtStrategy(opts, function (jwt_payload: any, done: VerifiedCallback) {\n verifyUserCallback(userService)(jwt_payload, done);\n }),\n );\n passport.use(new ApiKeyStrategy(apiKeyService));\n passport.use(new AnonymousStrategy());\n return passport;\n}\n"],"mappings":";;;;;;;;AAwBA,SAAgB,sBACd,eACA,eACA,iBAAiE,WAAW,6BAA6B,EAC1E;AAC/B,QAAO;EACW;EAChB,qBAAqB,OAAO,MAAM,QAAgB,SAAS;GACzD,MAAM,EAAE,cAAc,MAAM,cAAc,uBAAuB;AACjE,UAAO,KAAK,MAAM,UAAU;;EAE9B,UAAU,cAAc,IAAI,aAAa,uBAAuB,aAAa,qBAAqB;EAClG,QAAQ,cAAc,IAAI,aAAa,qBAAqB,aAAa,mBAAmB;EAC7F;;AAGH,SAAgB,mBAAmB,aAA2B;AAC5D,QAAO,SAAU,aAAkB,MAAwB;AACzD,cACG,QAAQ,YAAY,OAAO,CAC3B,MAAM,SAAe;AACpB,OAAI,MAAM,cAAc,CAAC,KAAK,oBAC5B,QAAO,KAAK,MAAM,YAAY,MAAM,KAAK,CAAC;AAE5C,OAAI,MAAM,oBACR,QAAO,KACL,IAAI,oBAAoB,4BAA4B,kBAAkB,uBAAuB,EAC7F,MACD;AAEH,OAAI,CAAC,MAAM,WACT,QAAO,KAAK,IAAI,oBAAoB,qBAAqB,kBAAkB,mBAAmB,EAAE,MAAM;AAGxG,UAAO,KAAK,MAAM,MAAM;IACxB,CACD,OAAO,QAAQ;AACd,OAAI,IACF,QAAO,KAAK,KAAK,MAAM;IAEzB;;;AAIR,SAAgB,6BAA6B,UAA0B,WAA4C;CACjH,MAAM,gBAAgB,UAAU,QAAuB,SAAS,cAAc;CAC9E,MAAM,gBAAgB,UAAU,QAAuB,SAAS,cAAc;CAC9E,MAAM,cAAc,UAAU,QAAsB,SAAS,YAAY;CACzE,MAAM,gBAAgB,UAAU,QAAwB,SAAS,cAAc;CAE/E,MAAM,OAAO,sBAAsB,eAAe,eAAe,WAAW,6BAA6B,CAAC;AAE1G,UAAS,IACP,IAAIA,SAAY,MAAM,SAAU,aAAkB,MAAwB;AACxE,qBAAmB,YAAY,CAAC,aAAa,KAAK;GAClD,CACH;AACD,UAAS,IAAI,IAAI,eAAe,cAAc,CAAC;AAC/C,UAAS,IAAI,IAAIC,YAAmB,CAAC;AACrC,QAAO"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/migrations/1778446203015-AddApiKey.ts
|
|
2
|
+
var AddApiKey1778446203015 = class {
|
|
3
|
+
name = "AddApiKey1778446203015";
|
|
4
|
+
async up(queryRunner) {
|
|
5
|
+
await queryRunner.query(`
|
|
6
|
+
CREATE TABLE "api_key"
|
|
7
|
+
(
|
|
8
|
+
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
9
|
+
"createdByUserId" integer NOT NULL,
|
|
10
|
+
"label" varchar NOT NULL,
|
|
11
|
+
"prefix" varchar NOT NULL,
|
|
12
|
+
"hashedSecret" varchar NOT NULL,
|
|
13
|
+
"createdAt" datetime NOT NULL DEFAULT (datetime('now')),
|
|
14
|
+
"lastUsedAt" datetime,
|
|
15
|
+
CONSTRAINT "FK_api_key_created_by_user" FOREIGN KEY ("createdByUserId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION
|
|
16
|
+
)
|
|
17
|
+
`);
|
|
18
|
+
await queryRunner.query(`
|
|
19
|
+
CREATE UNIQUE INDEX "IDX_api_key_prefix" ON "api_key" ("prefix")
|
|
20
|
+
`);
|
|
21
|
+
await queryRunner.query(`
|
|
22
|
+
CREATE TABLE "api_key_role"
|
|
23
|
+
(
|
|
24
|
+
"apiKeyId" integer NOT NULL,
|
|
25
|
+
"roleId" integer NOT NULL,
|
|
26
|
+
PRIMARY KEY ("apiKeyId", "roleId"),
|
|
27
|
+
CONSTRAINT "FK_api_key_role_api_key" FOREIGN KEY ("apiKeyId") REFERENCES "api_key" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
|
|
28
|
+
CONSTRAINT "FK_api_key_role_role" FOREIGN KEY ("roleId") REFERENCES "role" ("id") ON DELETE CASCADE ON UPDATE NO ACTION
|
|
29
|
+
)
|
|
30
|
+
`);
|
|
31
|
+
await queryRunner.query(`
|
|
32
|
+
CREATE INDEX "IDX_api_key_role_apiKeyId" ON "api_key_role" ("apiKeyId")
|
|
33
|
+
`);
|
|
34
|
+
await queryRunner.query(`
|
|
35
|
+
CREATE INDEX "IDX_api_key_role_roleId" ON "api_key_role" ("roleId")
|
|
36
|
+
`);
|
|
37
|
+
}
|
|
38
|
+
async down(queryRunner) {
|
|
39
|
+
await queryRunner.query(`DROP INDEX "IDX_api_key_role_roleId"`);
|
|
40
|
+
await queryRunner.query(`DROP INDEX "IDX_api_key_role_apiKeyId"`);
|
|
41
|
+
await queryRunner.query(`DROP TABLE "api_key_role"`);
|
|
42
|
+
await queryRunner.query(`DROP INDEX "IDX_api_key_prefix"`);
|
|
43
|
+
await queryRunner.query(`DROP TABLE "api_key"`);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
export { AddApiKey1778446203015 };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=1778446203015-AddApiKey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"1778446203015-AddApiKey.js","names":[],"sources":["../../src/migrations/1778446203015-AddApiKey.ts"],"sourcesContent":["import { MigrationInterface, QueryRunner } from \"typeorm\";\n\nexport class AddApiKey1778446203015 implements MigrationInterface {\n name = \"AddApiKey1778446203015\";\n\n public async up(queryRunner: QueryRunner): Promise<void> {\n await queryRunner.query(`\n CREATE TABLE \"api_key\"\n (\n \"id\" integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n \"createdByUserId\" integer NOT NULL,\n \"label\" varchar NOT NULL,\n \"prefix\" varchar NOT NULL,\n \"hashedSecret\" varchar NOT NULL,\n \"createdAt\" datetime NOT NULL DEFAULT (datetime('now')),\n \"lastUsedAt\" datetime,\n CONSTRAINT \"FK_api_key_created_by_user\" FOREIGN KEY (\"createdByUserId\") REFERENCES \"user\" (\"id\") ON DELETE CASCADE ON UPDATE NO ACTION\n )\n `);\n await queryRunner.query(`\n CREATE UNIQUE INDEX \"IDX_api_key_prefix\" ON \"api_key\" (\"prefix\")\n `);\n await queryRunner.query(`\n CREATE TABLE \"api_key_role\"\n (\n \"apiKeyId\" integer NOT NULL,\n \"roleId\" integer NOT NULL,\n PRIMARY KEY (\"apiKeyId\", \"roleId\"),\n CONSTRAINT \"FK_api_key_role_api_key\" FOREIGN KEY (\"apiKeyId\") REFERENCES \"api_key\" (\"id\") ON DELETE CASCADE ON UPDATE NO ACTION,\n CONSTRAINT \"FK_api_key_role_role\" FOREIGN KEY (\"roleId\") REFERENCES \"role\" (\"id\") ON DELETE CASCADE ON UPDATE NO ACTION\n )\n `);\n await queryRunner.query(`\n CREATE INDEX \"IDX_api_key_role_apiKeyId\" ON \"api_key_role\" (\"apiKeyId\")\n `);\n await queryRunner.query(`\n CREATE INDEX \"IDX_api_key_role_roleId\" ON \"api_key_role\" (\"roleId\")\n `);\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n await queryRunner.query(`DROP INDEX \"IDX_api_key_role_roleId\"`);\n await queryRunner.query(`DROP INDEX \"IDX_api_key_role_apiKeyId\"`);\n await queryRunner.query(`DROP TABLE \"api_key_role\"`);\n await queryRunner.query(`DROP INDEX \"IDX_api_key_prefix\"`);\n await queryRunner.query(`DROP TABLE \"api_key\"`);\n }\n}\n"],"mappings":";AAEA,IAAa,yBAAb,MAAkE;CAChE,OAAO;CAEP,MAAa,GAAG,aAAyC;AACvD,QAAM,YAAY,MAAM;;;;;;;;;;;;MAYtB;AACF,QAAM,YAAY,MAAM;;MAEtB;AACF,QAAM,YAAY,MAAM;;;;;;;;;MAStB;AACF,QAAM,YAAY,MAAM;;MAEtB;AACF,QAAM,YAAY,MAAM;;MAEtB;;CAGJ,MAAa,KAAK,aAAyC;AACzD,QAAM,YAAY,MAAM,uCAAuC;AAC/D,QAAM,YAAY,MAAM,yCAAyC;AACjE,QAAM,YAAY,MAAM,4BAA4B;AACpD,QAAM,YAAY,MAAM,kCAAkC;AAC1D,QAAM,YAAY,MAAM,uBAAuB"}
|
package/dist/server.constants.js
CHANGED
|
@@ -61,7 +61,7 @@ const AppConstants = {
|
|
|
61
61
|
docsUrl: "https://docs.fdm-monster.net",
|
|
62
62
|
orgName: "fdm-monster",
|
|
63
63
|
currentWizardVersion: 1,
|
|
64
|
-
defaultClientMinimum: "2.
|
|
64
|
+
defaultClientMinimum: "2.4.0",
|
|
65
65
|
defaultWebsocketHandshakeTimeout: 3e3,
|
|
66
66
|
defaultSocketThrottleRate: 1,
|
|
67
67
|
debugSocketStatesKey: "DEBUG_SOCKET_STATES",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.constants.js","names":[],"sources":["../src/server.constants.ts"],"sourcesContent":["export const AppConstants = {\n NODE_ENV_KEY: \"NODE_ENV\",\n VERSION_KEY: \"npm_package_version\",\n SERVER_PORT_KEY: \"SERVER_PORT\",\n DATABASE_PATH: \"DATABASE_PATH\",\n defaultDatabasePath: \"./database\",\n DATABASE_FILE: \"DATABASE_FILE\",\n defaultDatabaseFile: \"./fdm-monster.sqlite\",\n\n pm2ServiceName: \"FDM\",\n logAppName: \"fdm-monster\",\n\n MEDIA_PATH: \"MEDIA_PATH\",\n defaultBaseMediaPath: \"./media\",\n defaultLogsFolder: \"logs\",\n defaultLogZipsFolder: \"log-zips\",\n // New place for all downloads, files etc\n defaultClientBundleStorage: \"client-dist\",\n defaultClientBundleZipsStorage: \"client-dist-zips\",\n defaultPrinterThumbnailsStorage: \"printer-thumbnails\",\n defaultFileUploadsStorage: \"file-uploads\",\n defaultPrintFilesStorage: \"files\",\n defaultAcceptedGcodeExtensions: [\".gcode\", \".bgcode\"],\n defaultAcceptedBambuExtensions: [\".3mf\"],\n defaultServerPort: 4000,\n apiRoute: \"/api/v2\",\n enableClientDistAutoUpdateKey: \"ENABLE_CLIENT_DIST_AUTO_UPDATE\",\n\n // Boolean string (true/false), persisted always\n OVERRIDE_LOGIN_REQUIRED: \"OVERRIDE_LOGIN_REQUIRED\",\n // Boolean string (true/false), persisted always\n OVERRIDE_REGISTRATION_ENABLED: \"OVERRIDE_REGISTRATION_ENABLED\",\n // Number\n DEFAULT_USERNAME_MINLEN: 3,\n // Number\n DEFAULT_PASSWORD_MINLEN: 8,\n // String, persisted always\n OVERRIDE_JWT_SECRET: \"OVERRIDE_JWT_SECRET\",\n // Number, Seconds, persisted always\n OVERRIDE_JWT_EXPIRES_IN: \"OVERRIDE_JWT_EXPIRES_IN\",\n DEFAULT_JWT_EXPIRES_IN: 60 * 60, // 1 hour\n // Number\n DEFAULT_REFRESH_TOKEN_ATTEMPTS: -1, // 50 attempts, 50 hours\n // Number, Milli-seconds\n DEFAULT_REFRESH_TOKEN_EXPIRY: 60 * 60 * 24 * 14, // 14 days (in ms)\n // String, not persisted\n OVERRIDE_JWT_ISSUER: \"OVERRIDE_JWT_ISSUER\",\n DEFAULT_JWT_ISSUER: \"fdm-monster-server\",\n // String, not persisted\n OVERRIDE_JWT_AUDIENCE: \"OVERRIDE_JWT_AUDIENCE\",\n DEFAULT_JWT_AUDIENCE: \"fdm-monster-client-next\",\n\n OVERRIDE_IS_DEMO_MODE: \"OVERRIDE_IS_DEMO_MODE\",\n OVERRIDE_DEMO_USERNAME: \"OVERRIDE_DEMO_USERNAME\",\n DEFAULT_DEMO_USERNAME: \"demo\",\n OVERRIDE_DEMO_PASSWORD: \"OVERRIDE_DEMO_PASSWORD\",\n DEFAULT_DEMO_PASSWORD: \"demo2023\",\n OVERRIDE_DEMO_ROLE: \"OVERRIDE_DEMO_ROLE\",\n DEFAULT_DEMO_ROLE: \"ADMIN\",\n\n defaultDevelopmentEnv: \"development\",\n ENABLE_COLORED_LOGS_KEY: \"ENABLE_COLORED_LOGS\",\n defaultTestEnv: \"test\",\n defaultProductionEnv: \"production\",\n knownEnvNames: [\"development\", \"production\", \"test\"],\n GITHUB_PAT: \"GITHUB_PAT\",\n serverPackageName: \"@fdm-monster/server\",\n serverRepoName: \"fdm-monster\",\n clientPackageName: \"@fdm-monster/client-next\",\n clientRepoName: \"fdm-monster-client-next\",\n githubUrl: \"https://github.com/fdm-monster/fdm-monster\",\n docsUrl: \"https://docs.fdm-monster.net\",\n orgName: \"fdm-monster\",\n // Wizard version changes will trigger a re-run of the wizard\n currentWizardVersion: 1,\n defaultClientMinimum: \"2.
|
|
1
|
+
{"version":3,"file":"server.constants.js","names":[],"sources":["../src/server.constants.ts"],"sourcesContent":["export const AppConstants = {\n NODE_ENV_KEY: \"NODE_ENV\",\n VERSION_KEY: \"npm_package_version\",\n SERVER_PORT_KEY: \"SERVER_PORT\",\n DATABASE_PATH: \"DATABASE_PATH\",\n defaultDatabasePath: \"./database\",\n DATABASE_FILE: \"DATABASE_FILE\",\n defaultDatabaseFile: \"./fdm-monster.sqlite\",\n\n pm2ServiceName: \"FDM\",\n logAppName: \"fdm-monster\",\n\n MEDIA_PATH: \"MEDIA_PATH\",\n defaultBaseMediaPath: \"./media\",\n defaultLogsFolder: \"logs\",\n defaultLogZipsFolder: \"log-zips\",\n // New place for all downloads, files etc\n defaultClientBundleStorage: \"client-dist\",\n defaultClientBundleZipsStorage: \"client-dist-zips\",\n defaultPrinterThumbnailsStorage: \"printer-thumbnails\",\n defaultFileUploadsStorage: \"file-uploads\",\n defaultPrintFilesStorage: \"files\",\n defaultAcceptedGcodeExtensions: [\".gcode\", \".bgcode\"],\n defaultAcceptedBambuExtensions: [\".3mf\"],\n defaultServerPort: 4000,\n apiRoute: \"/api/v2\",\n enableClientDistAutoUpdateKey: \"ENABLE_CLIENT_DIST_AUTO_UPDATE\",\n\n // Boolean string (true/false), persisted always\n OVERRIDE_LOGIN_REQUIRED: \"OVERRIDE_LOGIN_REQUIRED\",\n // Boolean string (true/false), persisted always\n OVERRIDE_REGISTRATION_ENABLED: \"OVERRIDE_REGISTRATION_ENABLED\",\n // Number\n DEFAULT_USERNAME_MINLEN: 3,\n // Number\n DEFAULT_PASSWORD_MINLEN: 8,\n // String, persisted always\n OVERRIDE_JWT_SECRET: \"OVERRIDE_JWT_SECRET\",\n // Number, Seconds, persisted always\n OVERRIDE_JWT_EXPIRES_IN: \"OVERRIDE_JWT_EXPIRES_IN\",\n DEFAULT_JWT_EXPIRES_IN: 60 * 60, // 1 hour\n // Number\n DEFAULT_REFRESH_TOKEN_ATTEMPTS: -1, // 50 attempts, 50 hours\n // Number, Milli-seconds\n DEFAULT_REFRESH_TOKEN_EXPIRY: 60 * 60 * 24 * 14, // 14 days (in ms)\n // String, not persisted\n OVERRIDE_JWT_ISSUER: \"OVERRIDE_JWT_ISSUER\",\n DEFAULT_JWT_ISSUER: \"fdm-monster-server\",\n // String, not persisted\n OVERRIDE_JWT_AUDIENCE: \"OVERRIDE_JWT_AUDIENCE\",\n DEFAULT_JWT_AUDIENCE: \"fdm-monster-client-next\",\n\n OVERRIDE_IS_DEMO_MODE: \"OVERRIDE_IS_DEMO_MODE\",\n OVERRIDE_DEMO_USERNAME: \"OVERRIDE_DEMO_USERNAME\",\n DEFAULT_DEMO_USERNAME: \"demo\",\n OVERRIDE_DEMO_PASSWORD: \"OVERRIDE_DEMO_PASSWORD\",\n DEFAULT_DEMO_PASSWORD: \"demo2023\",\n OVERRIDE_DEMO_ROLE: \"OVERRIDE_DEMO_ROLE\",\n DEFAULT_DEMO_ROLE: \"ADMIN\",\n\n defaultDevelopmentEnv: \"development\",\n ENABLE_COLORED_LOGS_KEY: \"ENABLE_COLORED_LOGS\",\n defaultTestEnv: \"test\",\n defaultProductionEnv: \"production\",\n knownEnvNames: [\"development\", \"production\", \"test\"],\n GITHUB_PAT: \"GITHUB_PAT\",\n serverPackageName: \"@fdm-monster/server\",\n serverRepoName: \"fdm-monster\",\n clientPackageName: \"@fdm-monster/client-next\",\n clientRepoName: \"fdm-monster-client-next\",\n githubUrl: \"https://github.com/fdm-monster/fdm-monster\",\n docsUrl: \"https://docs.fdm-monster.net\",\n orgName: \"fdm-monster\",\n // Wizard version changes will trigger a re-run of the wizard\n currentWizardVersion: 1,\n defaultClientMinimum: \"2.4.0\",\n\n // Websocket values\n defaultWebsocketHandshakeTimeout: 3000,\n defaultSocketThrottleRate: 1,\n debugSocketStatesKey: \"DEBUG_SOCKET_STATES\",\n defaultDebugSocketStates: \"false\",\n\n // MonsterPi\n monsterPiFilePath: \"/etc/monsterpi_version\",\n\n // Sentry\n sentryCustomDsnToken: \"SENTRY_CUSTOM_DSN\",\n sentryCustomDsnDefault:\n \"https://164b8028a8a745bba3dbcab991b84ae7@o4503975545733120.ingest.sentry.io/4505101598261248\",\n\n ENABLE_PROMETHEUS_METRICS: \"ENABLE_PROMETHEUS_METRICS\",\n ENABLE_LOKI_LOGGING: \"ENABLE_LOKI_LOGGING\",\n LOKI_ADDRESS: \"LOKI_ADDRESS\",\n LOKI_TIMEOUT_SECONDS: \"LOKI_TIMEOUT_SECONDS\",\n LOKI_INTERVAL: \"LOKI_INTERVAL\",\n\n // Swagger/OpenAPI Documentation\n DISABLE_SWAGGER_OPENAPI: \"DISABLE_SWAGGER_OPENAPI\",\n GENERATE_SWAGGER_JSON: \"GENERATE_SWAGGER_JSON\",\n};\n"],"mappings":";AAAA,MAAa,eAAe;CAC1B,cAAc;CACd,aAAa;CACb,iBAAiB;CACjB,eAAe;CACf,qBAAqB;CACrB,eAAe;CACf,qBAAqB;CAErB,gBAAgB;CAChB,YAAY;CAEZ,YAAY;CACZ,sBAAsB;CACtB,mBAAmB;CACnB,sBAAsB;CAEtB,4BAA4B;CAC5B,gCAAgC;CAChC,iCAAiC;CACjC,2BAA2B;CAC3B,0BAA0B;CAC1B,gCAAgC,CAAC,UAAU,UAAU;CACrD,gCAAgC,CAAC,OAAO;CACxC,mBAAmB;CACnB,UAAU;CACV,+BAA+B;CAG/B,yBAAyB;CAEzB,+BAA+B;CAE/B,yBAAyB;CAEzB,yBAAyB;CAEzB,qBAAqB;CAErB,yBAAyB;CACzB,wBAAwB;CAExB,gCAAgC;CAEhC,8BAA8B,OAAU,KAAK;CAE7C,qBAAqB;CACrB,oBAAoB;CAEpB,uBAAuB;CACvB,sBAAsB;CAEtB,uBAAuB;CACvB,wBAAwB;CACxB,uBAAuB;CACvB,wBAAwB;CACxB,uBAAuB;CACvB,oBAAoB;CACpB,mBAAmB;CAEnB,uBAAuB;CACvB,yBAAyB;CACzB,gBAAgB;CAChB,sBAAsB;CACtB,eAAe;EAAC;EAAe;EAAc;EAAO;CACpD,YAAY;CACZ,mBAAmB;CACnB,gBAAgB;CAChB,mBAAmB;CACnB,gBAAgB;CAChB,WAAW;CACX,SAAS;CACT,SAAS;CAET,sBAAsB;CACtB,sBAAsB;CAGtB,kCAAkC;CAClC,2BAA2B;CAC3B,sBAAsB;CACtB,0BAA0B;CAG1B,mBAAmB;CAGnB,sBAAsB;CACtB,wBACE;CAEF,2BAA2B;CAC3B,qBAAqB;CACrB,cAAc;CACd,sBAAsB;CACtB,eAAe;CAGf,yBAAyB;CACzB,uBAAuB;CACxB"}
|
package/dist/server.core.js
CHANGED
|
@@ -4,11 +4,11 @@ import { LoggerService } from "./handlers/logger.js";
|
|
|
4
4
|
import { configureContainer } from "./container.js";
|
|
5
5
|
import { interceptDatabaseError } from "./middleware/database.js";
|
|
6
6
|
import { interceptRoles, validateWizardCompleted } from "./middleware/global.middleware.js";
|
|
7
|
+
import passport from "passport";
|
|
7
8
|
import { Counter } from "prom-client";
|
|
8
9
|
import express, { json, urlencoded } from "express";
|
|
9
10
|
import { scopePerRequest } from "awilix-express";
|
|
10
11
|
import cookieParser from "cookie-parser";
|
|
11
|
-
import passport from "passport";
|
|
12
12
|
import cors from "cors";
|
|
13
13
|
import helmet from "helmet";
|
|
14
14
|
//#region src/server.core.ts
|
|
@@ -49,7 +49,11 @@ async function setupServer() {
|
|
|
49
49
|
}).use(cors({
|
|
50
50
|
origin: "*",
|
|
51
51
|
methods: "GET,HEAD,PUT,PATCH,POST,DELETE"
|
|
52
|
-
})).use(helmet({ contentSecurityPolicy: false })).use(json({ limit: "10mb" })).use(cookieParser()).use(urlencoded({ extended: false })).use(passport.initialize()).use(passport.authenticate([
|
|
52
|
+
})).use(helmet({ contentSecurityPolicy: false })).use(json({ limit: "10mb" })).use(cookieParser()).use(urlencoded({ extended: false })).use(passport.initialize()).use(passport.authenticate([
|
|
53
|
+
"jwt",
|
|
54
|
+
"api-key",
|
|
55
|
+
"anonymous"
|
|
56
|
+
], { session: false })).use(scopePerRequest(container)).use(interceptDatabaseError).use(validateWizardCompleted).use(interceptRoles);
|
|
53
57
|
return {
|
|
54
58
|
httpServer,
|
|
55
59
|
container
|
package/dist/server.core.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.core.js","names":[],"sources":["../src/server.core.ts"],"sourcesContent":["import express, { json, urlencoded } from \"express\";\nimport cookieParser from \"cookie-parser\";\nimport passport from \"passport\";\nimport cors from \"cors\";\nimport helmet from \"helmet\";\nimport { scopePerRequest } from \"awilix-express\";\nimport { configureContainer } from \"./container\";\nimport { interceptDatabaseError } from \"./middleware/database\";\nimport { interceptRoles, validateWizardCompleted } from \"./middleware/global.middleware\";\nimport { initializePassportStrategies } from \"./middleware/passport\";\nimport { ensureDirExists, getDatabaseFolder, getMediaPath } from \"@/utils/fs.utils\";\nimport { Counter } from \"prom-client\";\nimport { LoggerService } from \"@/handlers/logger\";\n\nconst httpRequestsTotal = new Counter({\n name: \"http_requests_total\",\n help: \"HTTP requests executed\",\n});\n\nexport async function setupServer() {\n const logger = new LoggerService(\"FDM-ServerCore\");\n const httpServer = express();\n\n const databasePath = getDatabaseFolder();\n ensureDirExists(databasePath);\n logger.log(`Expecting database at path ${databasePath}`);\n\n const mediaPath = getMediaPath();\n ensureDirExists(mediaPath);\n logger.log(`Expecting media at path ${mediaPath}`);\n\n const container = configureContainer();\n initializePassportStrategies(passport, container);\n\n httpServer\n .use((req, res, next) => {\n const route = req.route?.path ?? req.path ?? \"unknown\";\n\n if (route.includes(\"/api\")) {\n const start = process.hrtime();\n\n res.on(\"finish\", () => {\n httpRequestsTotal.inc();\n\n const delta = process.hrtime(start);\n const duration = delta[0] + delta[1] / 1e9;\n const logger = new LoggerService(\"HttpRequest\");\n logger.newDebug({\n message: `HTTP request ${req.method} ${req.originalUrl} ${res.statusCode}`,\n method: req.method,\n path: req.originalUrl,\n statusCode: res.statusCode,\n responseTimeMs: duration.toFixed(2),\n clientIp: req.ip,\n userAgent: req.get(\"User-Agent\"),\n });\n });\n }\n\n next();\n })\n .use(\n cors({\n origin: \"*\",\n methods: \"GET,HEAD,PUT,PATCH,POST,DELETE\",\n }),\n )\n .use(\n helmet({\n contentSecurityPolicy: false,\n }),\n )\n .use(json({ limit: \"10mb\" }))\n .use(cookieParser())\n .use(urlencoded({ extended: false }))\n .use(passport.initialize())\n .use(passport.authenticate([\"jwt\", \"anonymous\"], { session: false }))\n .use(scopePerRequest(container))\n .use(interceptDatabaseError)\n // Global guards\n .use(validateWizardCompleted)\n .use(interceptRoles);\n\n return {\n httpServer,\n container,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAM,oBAAoB,IAAI,QAAQ;CACpC,MAAM;CACN,MAAM;CACP,CAAC;AAEF,eAAsB,cAAc;CAClC,MAAM,SAAS,IAAI,cAAc,iBAAiB;CAClD,MAAM,aAAa,SAAS;CAE5B,MAAM,eAAe,mBAAmB;AACxC,iBAAgB,aAAa;AAC7B,QAAO,IAAI,8BAA8B,eAAe;CAExD,MAAM,YAAY,cAAc;AAChC,iBAAgB,UAAU;AAC1B,QAAO,IAAI,2BAA2B,YAAY;CAElD,MAAM,YAAY,oBAAoB;AACtC,8BAA6B,UAAU,UAAU;AAEjD,YACG,KAAK,KAAK,KAAK,SAAS;AAGvB,OAFc,IAAI,OAAO,QAAQ,IAAI,QAAQ,WAEnC,SAAS,OAAO,EAAE;GAC1B,MAAM,QAAQ,QAAQ,QAAQ;AAE9B,OAAI,GAAG,gBAAgB;AACrB,sBAAkB,KAAK;IAEvB,MAAM,QAAQ,QAAQ,OAAO,MAAM;IACnC,MAAM,WAAW,MAAM,KAAK,MAAM,KAAK;AAEvC,QADmB,cAAc,cAC3B,CAAC,SAAS;KACd,SAAS,gBAAgB,IAAI,OAAO,GAAG,IAAI,YAAY,GAAG,IAAI;KAC9D,QAAQ,IAAI;KACZ,MAAM,IAAI;KACV,YAAY,IAAI;KAChB,gBAAgB,SAAS,QAAQ,EAAE;KACnC,UAAU,IAAI;KACd,WAAW,IAAI,IAAI,aAAa;KACjC,CAAC;KACF;;AAGJ,QAAM;GACN,CACD,IACC,KAAK;EACH,QAAQ;EACR,SAAS;EACV,CAAC,CACH,CACA,IACC,OAAO,EACL,uBAAuB,OACxB,CAAC,CACH,CACA,IAAI,KAAK,EAAE,OAAO,QAAQ,CAAC,CAAC,CAC5B,IAAI,cAAc,CAAC,CACnB,IAAI,WAAW,EAAE,UAAU,OAAO,CAAC,CAAC,CACpC,IAAI,SAAS,YAAY,CAAC,CAC1B,IAAI,SAAS,aAAa,
|
|
1
|
+
{"version":3,"file":"server.core.js","names":[],"sources":["../src/server.core.ts"],"sourcesContent":["import express, { json, urlencoded } from \"express\";\nimport cookieParser from \"cookie-parser\";\nimport passport from \"passport\";\nimport cors from \"cors\";\nimport helmet from \"helmet\";\nimport { scopePerRequest } from \"awilix-express\";\nimport { configureContainer } from \"./container\";\nimport { interceptDatabaseError } from \"./middleware/database\";\nimport { interceptRoles, validateWizardCompleted } from \"./middleware/global.middleware\";\nimport { initializePassportStrategies } from \"./middleware/passport\";\nimport { ensureDirExists, getDatabaseFolder, getMediaPath } from \"@/utils/fs.utils\";\nimport { Counter } from \"prom-client\";\nimport { LoggerService } from \"@/handlers/logger\";\n\nconst httpRequestsTotal = new Counter({\n name: \"http_requests_total\",\n help: \"HTTP requests executed\",\n});\n\nexport async function setupServer() {\n const logger = new LoggerService(\"FDM-ServerCore\");\n const httpServer = express();\n\n const databasePath = getDatabaseFolder();\n ensureDirExists(databasePath);\n logger.log(`Expecting database at path ${databasePath}`);\n\n const mediaPath = getMediaPath();\n ensureDirExists(mediaPath);\n logger.log(`Expecting media at path ${mediaPath}`);\n\n const container = configureContainer();\n initializePassportStrategies(passport, container);\n\n httpServer\n .use((req, res, next) => {\n const route = req.route?.path ?? req.path ?? \"unknown\";\n\n if (route.includes(\"/api\")) {\n const start = process.hrtime();\n\n res.on(\"finish\", () => {\n httpRequestsTotal.inc();\n\n const delta = process.hrtime(start);\n const duration = delta[0] + delta[1] / 1e9;\n const logger = new LoggerService(\"HttpRequest\");\n logger.newDebug({\n message: `HTTP request ${req.method} ${req.originalUrl} ${res.statusCode}`,\n method: req.method,\n path: req.originalUrl,\n statusCode: res.statusCode,\n responseTimeMs: duration.toFixed(2),\n clientIp: req.ip,\n userAgent: req.get(\"User-Agent\"),\n });\n });\n }\n\n next();\n })\n .use(\n cors({\n origin: \"*\",\n methods: \"GET,HEAD,PUT,PATCH,POST,DELETE\",\n }),\n )\n .use(\n helmet({\n contentSecurityPolicy: false,\n }),\n )\n .use(json({ limit: \"10mb\" }))\n .use(cookieParser())\n .use(urlencoded({ extended: false }))\n .use(passport.initialize())\n .use(passport.authenticate([\"jwt\", \"api-key\", \"anonymous\"], { session: false }))\n .use(scopePerRequest(container))\n .use(interceptDatabaseError)\n // Global guards\n .use(validateWizardCompleted)\n .use(interceptRoles);\n\n return {\n httpServer,\n container,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAM,oBAAoB,IAAI,QAAQ;CACpC,MAAM;CACN,MAAM;CACP,CAAC;AAEF,eAAsB,cAAc;CAClC,MAAM,SAAS,IAAI,cAAc,iBAAiB;CAClD,MAAM,aAAa,SAAS;CAE5B,MAAM,eAAe,mBAAmB;AACxC,iBAAgB,aAAa;AAC7B,QAAO,IAAI,8BAA8B,eAAe;CAExD,MAAM,YAAY,cAAc;AAChC,iBAAgB,UAAU;AAC1B,QAAO,IAAI,2BAA2B,YAAY;CAElD,MAAM,YAAY,oBAAoB;AACtC,8BAA6B,UAAU,UAAU;AAEjD,YACG,KAAK,KAAK,KAAK,SAAS;AAGvB,OAFc,IAAI,OAAO,QAAQ,IAAI,QAAQ,WAEnC,SAAS,OAAO,EAAE;GAC1B,MAAM,QAAQ,QAAQ,QAAQ;AAE9B,OAAI,GAAG,gBAAgB;AACrB,sBAAkB,KAAK;IAEvB,MAAM,QAAQ,QAAQ,OAAO,MAAM;IACnC,MAAM,WAAW,MAAM,KAAK,MAAM,KAAK;AAEvC,QADmB,cAAc,cAC3B,CAAC,SAAS;KACd,SAAS,gBAAgB,IAAI,OAAO,GAAG,IAAI,YAAY,GAAG,IAAI;KAC9D,QAAQ,IAAI;KACZ,MAAM,IAAI;KACV,YAAY,IAAI;KAChB,gBAAgB,SAAS,QAAQ,EAAE;KACnC,UAAU,IAAI;KACd,WAAW,IAAI,IAAI,aAAa;KACjC,CAAC;KACF;;AAGJ,QAAM;GACN,CACD,IACC,KAAK;EACH,QAAQ;EACR,SAAS;EACV,CAAC,CACH,CACA,IACC,OAAO,EACL,uBAAuB,OACxB,CAAC,CACH,CACA,IAAI,KAAK,EAAE,OAAO,QAAQ,CAAC,CAAC,CAC5B,IAAI,cAAc,CAAC,CACnB,IAAI,WAAW,EAAE,UAAU,OAAO,CAAC,CAAC,CACpC,IAAI,SAAS,YAAY,CAAC,CAC1B,IAAI,SAAS,aAAa;EAAC;EAAO;EAAW;EAAY,EAAE,EAAE,SAAS,OAAO,CAAC,CAAC,CAC/E,IAAI,gBAAgB,UAAU,CAAC,CAC/B,IAAI,uBAAuB,CAE3B,IAAI,wBAAwB,CAC5B,IAAI,eAAe;AAEtB,QAAO;EACL;EACA;EACD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/services/interfaces/api-key.dto.ts
|
|
2
|
+
/** Public DTO. Never includes the cleartext token or the stored hash. */
|
|
3
|
+
var ApiKeyDto = class {
|
|
4
|
+
id;
|
|
5
|
+
createdByUserId;
|
|
6
|
+
label;
|
|
7
|
+
prefix;
|
|
8
|
+
createdAt;
|
|
9
|
+
lastUsedAt;
|
|
10
|
+
roles;
|
|
11
|
+
};
|
|
12
|
+
/** Returned exactly once at creation. */
|
|
13
|
+
var CreatedApiKeyDto = class extends ApiKeyDto {
|
|
14
|
+
token;
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { ApiKeyDto, CreatedApiKeyDto };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=api-key.dto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.dto.js","names":[],"sources":["../../../src/services/interfaces/api-key.dto.ts"],"sourcesContent":["/** Public DTO. Never includes the cleartext token or the stored hash. */\nexport class ApiKeyDto {\n id!: number;\n createdByUserId!: number;\n label!: string;\n prefix!: string;\n createdAt!: Date;\n lastUsedAt!: Date | null;\n roles!: string[];\n}\n\n/** Returned exactly once at creation. */\nexport class CreatedApiKeyDto extends ApiKeyDto {\n token!: string;\n}\n"],"mappings":";;AACA,IAAa,YAAb,MAAuB;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;;;AAIF,IAAa,mBAAb,cAAsC,UAAU;CAC9C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user.dto.js","names":[],"sources":["../../../src/services/interfaces/user.dto.ts"],"sourcesContent":["import type { RoleName } from \"@/constants/authorization.constants\";\n\nexport class UserDto {\n id: number;\n createdAt: Date;\n username: string;\n isDemoUser: boolean;\n isRootUser: boolean;\n isVerified: boolean;\n needsPasswordChange: boolean;\n roles: RoleName[];\n}\n\nexport class RegisterUserDto {\n username: string;\n password: string;\n roles: RoleName[];\n isDemoUser: boolean;\n isRootUser: boolean;\n isVerified: boolean;\n needsPasswordChange: boolean;\n}\n"],"mappings":";AAEA,IAAa,UAAb,MAAqB;CACnB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;;AAGF,IAAa,kBAAb,MAA6B;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA"}
|
|
1
|
+
{"version":3,"file":"user.dto.js","names":[],"sources":["../../../src/services/interfaces/user.dto.ts"],"sourcesContent":["import type { RoleName } from \"@/constants/authorization.constants\";\n\nexport class UserDto {\n id: number;\n createdAt: Date;\n username: string;\n isDemoUser: boolean;\n isRootUser: boolean;\n isVerified: boolean;\n needsPasswordChange: boolean;\n roles: RoleName[];\n /** True when the principal was authenticated via an API key, not a user login. */\n isApiKey?: boolean;\n}\n\nexport class RegisterUserDto {\n username: string;\n password: string;\n roles: RoleName[];\n isDemoUser: boolean;\n isRootUser: boolean;\n isVerified: boolean;\n needsPasswordChange: boolean;\n}\n"],"mappings":";AAEA,IAAa,UAAb,MAAqB;CACnB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;;CAEA;;AAGF,IAAa,kBAAb,MAA6B;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { NotFoundException } from "../../exceptions/runtime.exceptions.js";
|
|
2
|
+
import { BaseService } from "./base.service.js";
|
|
3
|
+
import { Role } from "../../entities/role.entity.js";
|
|
4
|
+
import { ApiKey } from "../../entities/api-key.entity.js";
|
|
5
|
+
import "../../entities/index.js";
|
|
6
|
+
import { ApiKeyDto } from "../interfaces/api-key.dto.js";
|
|
7
|
+
import { In } from "typeorm";
|
|
8
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
9
|
+
//#region src/services/orm/api-key.service.ts
|
|
10
|
+
const TOKEN_PREFIX = "fdmm_api_";
|
|
11
|
+
const SECRET_BYTES = 32;
|
|
12
|
+
const PREFIX_LEN = 16;
|
|
13
|
+
function generateToken() {
|
|
14
|
+
const secret = randomBytes(SECRET_BYTES).toString("base64url");
|
|
15
|
+
const token = `${TOKEN_PREFIX}${secret}`;
|
|
16
|
+
return {
|
|
17
|
+
token,
|
|
18
|
+
prefix: secret.slice(0, PREFIX_LEN),
|
|
19
|
+
hashedSecret: createHash("sha256").update(token).digest("hex")
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
var ApiKeyService = class ApiKeyService extends BaseService(ApiKey, ApiKeyDto) {
|
|
23
|
+
logger;
|
|
24
|
+
constructor(loggerFactory, typeormService) {
|
|
25
|
+
super(typeormService);
|
|
26
|
+
this.logger = loggerFactory(ApiKeyService.name);
|
|
27
|
+
}
|
|
28
|
+
get roleRepo() {
|
|
29
|
+
return this.typeormService.getDataSource().getRepository(Role);
|
|
30
|
+
}
|
|
31
|
+
toDto(entity) {
|
|
32
|
+
return {
|
|
33
|
+
id: entity.id,
|
|
34
|
+
createdByUserId: entity.createdByUserId,
|
|
35
|
+
label: entity.label,
|
|
36
|
+
prefix: entity.prefix,
|
|
37
|
+
createdAt: entity.createdAt,
|
|
38
|
+
lastUsedAt: entity.lastUsedAt,
|
|
39
|
+
roles: (entity.roles ?? []).map((r) => r.name)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
looksLikeApiKey(token) {
|
|
43
|
+
return typeof token === "string" && token.startsWith(TOKEN_PREFIX) && token.length > 9 + PREFIX_LEN;
|
|
44
|
+
}
|
|
45
|
+
async create(createdByUserId, label, roleIds) {
|
|
46
|
+
const trimmed = label?.trim();
|
|
47
|
+
if (!trimmed?.length) throw new Error("API key label is required");
|
|
48
|
+
if (!roleIds?.length) throw new Error("At least one role must be assigned to an API key");
|
|
49
|
+
const roles = await this.roleRepo.find({ where: { id: In(roleIds) } });
|
|
50
|
+
if (roles.length !== roleIds.length) throw new NotFoundException("One or more roleIds do not exist");
|
|
51
|
+
const { token, prefix, hashedSecret } = generateToken();
|
|
52
|
+
const entity = await this.repository.save(this.repository.create({
|
|
53
|
+
createdByUserId,
|
|
54
|
+
label: trimmed,
|
|
55
|
+
prefix,
|
|
56
|
+
hashedSecret,
|
|
57
|
+
lastUsedAt: null,
|
|
58
|
+
roles
|
|
59
|
+
}));
|
|
60
|
+
this.logger.log(`Created API key id=${entity.id} prefix=${prefix} by user ${createdByUserId} with roles ${roleIds}`);
|
|
61
|
+
return {
|
|
62
|
+
...this.toDto(entity),
|
|
63
|
+
token
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async list() {
|
|
67
|
+
return (await this.repository.find({ order: { createdAt: "DESC" } })).map((r) => this.toDto(r));
|
|
68
|
+
}
|
|
69
|
+
async delete(id) {
|
|
70
|
+
const row = await this.repository.findOneBy({ id });
|
|
71
|
+
if (!row) throw new NotFoundException(`API key ${id} not found`);
|
|
72
|
+
await this.repository.delete(id);
|
|
73
|
+
this.logger.log(`Deleted API key id=${id} prefix=${row.prefix}`);
|
|
74
|
+
}
|
|
75
|
+
async verify(token) {
|
|
76
|
+
if (!this.looksLikeApiKey(token)) return null;
|
|
77
|
+
const prefix = token.slice(9).slice(0, PREFIX_LEN);
|
|
78
|
+
const candidate = await this.repository.findOne({ where: { prefix } });
|
|
79
|
+
if (!candidate) return null;
|
|
80
|
+
const presented = createHash("sha256").update(token).digest();
|
|
81
|
+
const stored = Buffer.from(candidate.hashedSecret, "hex");
|
|
82
|
+
if (presented.length !== stored.length || !timingSafeEqual(presented, stored)) return null;
|
|
83
|
+
this.repository.update({ id: candidate.id }, { lastUsedAt: /* @__PURE__ */ new Date() }).catch((err) => this.logger.warn(`Failed to bump lastUsedAt for api key ${candidate.id}: ${err}`));
|
|
84
|
+
return candidate;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
88
|
+
export { ApiKeyService };
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=api-key.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.service.js","names":[],"sources":["../../../src/services/orm/api-key.service.ts"],"sourcesContent":["import { BaseService } from \"@/services/orm/base.service\";\nimport { ApiKey, Role } from \"@/entities\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport type { IApiKeyService } from \"@/services/interfaces/api-key.service.interface\";\nimport { ApiKeyDto, CreatedApiKeyDto } from \"@/services/interfaces/api-key.dto\";\nimport { NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { createHash, randomBytes, timingSafeEqual } from \"node:crypto\";\nimport { In } from \"typeorm\";\n\nconst TOKEN_PREFIX = \"fdmm_api_\";\nconst SECRET_BYTES = 32;\nconst PREFIX_LEN = 16;\n\nfunction generateToken(): { token: string; prefix: string; hashedSecret: string } {\n const secret = randomBytes(SECRET_BYTES).toString(\"base64url\");\n const token = `${TOKEN_PREFIX}${secret}`;\n const prefix = secret.slice(0, PREFIX_LEN);\n const hashedSecret = createHash(\"sha256\").update(token).digest(\"hex\");\n return { token, prefix, hashedSecret };\n}\n\nexport class ApiKeyService extends BaseService(ApiKey, ApiKeyDto) implements IApiKeyService {\n private readonly logger: LoggerService;\n\n constructor(loggerFactory: ILoggerFactory, typeormService: TypeormService) {\n super(typeormService);\n this.logger = loggerFactory(ApiKeyService.name);\n }\n\n private get roleRepo() {\n return this.typeormService.getDataSource().getRepository(Role);\n }\n\n toDto(entity: ApiKey): ApiKeyDto {\n return {\n id: entity.id,\n createdByUserId: entity.createdByUserId,\n label: entity.label,\n prefix: entity.prefix,\n createdAt: entity.createdAt,\n lastUsedAt: entity.lastUsedAt,\n roles: (entity.roles ?? []).map((r) => r.name),\n };\n }\n\n looksLikeApiKey(token: string): boolean {\n return (\n typeof token === \"string\" && token.startsWith(TOKEN_PREFIX) && token.length > TOKEN_PREFIX.length + PREFIX_LEN\n );\n }\n\n async create(createdByUserId: number, label: string, roleIds: number[]): Promise<CreatedApiKeyDto> {\n const trimmed = label?.trim();\n if (!trimmed?.length) {\n throw new Error(\"API key label is required\");\n }\n if (!roleIds?.length) {\n throw new Error(\"At least one role must be assigned to an API key\");\n }\n\n const roles = await this.roleRepo.find({ where: { id: In(roleIds) } });\n if (roles.length !== roleIds.length) {\n throw new NotFoundException(\"One or more roleIds do not exist\");\n }\n\n const { token, prefix, hashedSecret } = generateToken();\n const entity = await this.repository.save(\n this.repository.create({\n createdByUserId,\n label: trimmed,\n prefix,\n hashedSecret,\n lastUsedAt: null,\n roles,\n }),\n );\n\n this.logger.log(\n `Created API key id=${entity.id} prefix=${prefix} by user ${createdByUserId} with roles ${roleIds}`,\n );\n return {\n ...this.toDto(entity),\n token,\n };\n }\n\n async list(): Promise<ApiKeyDto[]> {\n const rows = await this.repository.find({ order: { createdAt: \"DESC\" } });\n return rows.map((r) => this.toDto(r));\n }\n\n async delete(id: number): Promise<void> {\n const row = await this.repository.findOneBy({ id });\n if (!row) {\n throw new NotFoundException(`API key ${id} not found`);\n }\n await this.repository.delete(id);\n this.logger.log(`Deleted API key id=${id} prefix=${row.prefix}`);\n }\n\n async verify(token: string): Promise<ApiKey | null> {\n if (!this.looksLikeApiKey(token)) return null;\n\n const secret = token.slice(TOKEN_PREFIX.length);\n const prefix = secret.slice(0, PREFIX_LEN);\n\n const candidate = await this.repository.findOne({ where: { prefix } });\n if (!candidate) return null;\n\n const presented = createHash(\"sha256\").update(token).digest();\n const stored = Buffer.from(candidate.hashedSecret, \"hex\");\n if (presented.length !== stored.length || !timingSafeEqual(presented, stored)) {\n return null;\n }\n\n this.repository\n .update({ id: candidate.id }, { lastUsedAt: new Date() })\n .catch((err) => this.logger.warn(`Failed to bump lastUsedAt for api key ${candidate.id}: ${err}`));\n\n return candidate;\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fdm-monster/server",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "FDM Monster is a bulk OctoPrint, Klipper, PrusaLink and BambuLab manager to set up, configure and monitor 3D printers. Our aim is to provide neat overview over your farm.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"3d printing",
|
|
@@ -54,16 +54,16 @@
|
|
|
54
54
|
"console:migrate": "tsx src/consoles/typeorm-migrate.ts"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@fdm-monster/client-next": "2.
|
|
57
|
+
"@fdm-monster/client-next": "2.4.0",
|
|
58
58
|
"@octokit/plugin-throttling": "11.0.3",
|
|
59
|
-
"@sentry/node": "10.
|
|
59
|
+
"@sentry/node": "10.53.1",
|
|
60
60
|
"adm-zip": "0.5.17",
|
|
61
61
|
"awilix": "13.0.3",
|
|
62
62
|
"awilix-express": "11.0.1",
|
|
63
|
-
"axios": "1.16.
|
|
63
|
+
"axios": "1.16.1",
|
|
64
64
|
"basic-ftp": "6.0.1",
|
|
65
65
|
"bcryptjs": "3.0.3",
|
|
66
|
-
"better-sqlite3": "12.
|
|
66
|
+
"better-sqlite3": "12.10.0",
|
|
67
67
|
"chalk": "5.6.2",
|
|
68
68
|
"class-validator": "0.15.1",
|
|
69
69
|
"connect-history-api-fallback": "2.0.0",
|
|
@@ -90,10 +90,10 @@
|
|
|
90
90
|
"socket.io": "4.8.3",
|
|
91
91
|
"toad-scheduler": "4.0.1",
|
|
92
92
|
"typeorm": "0.3.29",
|
|
93
|
-
"uuid": "
|
|
93
|
+
"uuid": "14.0.0",
|
|
94
94
|
"winston": "3.19.0",
|
|
95
95
|
"winston-loki": "6.1.4",
|
|
96
|
-
"ws": "8.20.
|
|
96
|
+
"ws": "8.20.1",
|
|
97
97
|
"zod": "3.25.76"
|
|
98
98
|
},
|
|
99
99
|
"devDependencies": {
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"@types/passport-jwt": "4.0.1",
|
|
113
113
|
"@types/semver": "7.7.1",
|
|
114
114
|
"@types/supertest": "7.2.0",
|
|
115
|
-
"@types/uuid": "
|
|
115
|
+
"@types/uuid": "11.0.0",
|
|
116
116
|
"@types/ws": "8.18.1",
|
|
117
117
|
"@vitest/coverage-v8": "4.1.6",
|
|
118
118
|
"@vitest/ui": "4.1.6",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"nock": "13.5.6",
|
|
121
121
|
"openapi3-ts": "4.5.0",
|
|
122
122
|
"supertest": "7.2.2",
|
|
123
|
-
"swagger-ui-dist": "5.32.
|
|
123
|
+
"swagger-ui-dist": "5.32.6",
|
|
124
124
|
"tsx": "4.21.0",
|
|
125
125
|
"typescript": "6.0.3",
|
|
126
126
|
"vite-plus": "latest",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"bambu:diagnostic": "cross-env NODE_ENV=development node --enable-source-maps dist/bambu-mqtt-diagnostic.console.js"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"axios": "1.16.
|
|
21
|
-
"better-sqlite3": "12.
|
|
20
|
+
"axios": "1.16.1",
|
|
21
|
+
"better-sqlite3": "12.10.0",
|
|
22
22
|
"chalk": "5.6.2",
|
|
23
23
|
"dotenv": "17.4.2",
|
|
24
24
|
"express": "4.22.2",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"multer": "2.1.1",
|
|
28
28
|
"selfsigned": "5.5.0",
|
|
29
29
|
"typeorm": "0.3.29",
|
|
30
|
-
"ws": "8.20.
|
|
30
|
+
"ws": "8.20.1",
|
|
31
31
|
"zod": "3.25.76"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|