digitaltwin-core 0.8.3 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/auth/apisix_parser.d.ts +24 -0
  2. package/dist/auth/apisix_parser.d.ts.map +1 -1
  3. package/dist/auth/apisix_parser.js +28 -0
  4. package/dist/auth/apisix_parser.js.map +1 -1
  5. package/dist/auth/auth_config.d.ts +31 -0
  6. package/dist/auth/auth_config.d.ts.map +1 -1
  7. package/dist/auth/auth_config.js +48 -4
  8. package/dist/auth/auth_config.js.map +1 -1
  9. package/dist/auth/user_service.d.ts.map +1 -1
  10. package/dist/auth/user_service.js +4 -1
  11. package/dist/auth/user_service.js.map +1 -1
  12. package/dist/components/assets_manager.d.ts +79 -8
  13. package/dist/components/assets_manager.d.ts.map +1 -1
  14. package/dist/components/assets_manager.js +353 -21
  15. package/dist/components/assets_manager.js.map +1 -1
  16. package/dist/components/custom_table_manager.d.ts +142 -1
  17. package/dist/components/custom_table_manager.d.ts.map +1 -1
  18. package/dist/components/custom_table_manager.js +313 -3
  19. package/dist/components/custom_table_manager.js.map +1 -1
  20. package/dist/components/map_manager.d.ts.map +1 -1
  21. package/dist/components/map_manager.js +9 -0
  22. package/dist/components/map_manager.js.map +1 -1
  23. package/dist/components/tileset_manager.d.ts.map +1 -1
  24. package/dist/components/tileset_manager.js +9 -0
  25. package/dist/components/tileset_manager.js.map +1 -1
  26. package/dist/components/types.d.ts +169 -19
  27. package/dist/components/types.d.ts.map +1 -1
  28. package/dist/components/types.js +6 -0
  29. package/dist/components/types.js.map +1 -1
  30. package/dist/database/adapters/knex_database_adapter.d.ts +10 -0
  31. package/dist/database/adapters/knex_database_adapter.d.ts.map +1 -1
  32. package/dist/database/adapters/knex_database_adapter.js +104 -9
  33. package/dist/database/adapters/knex_database_adapter.js.map +1 -1
  34. package/dist/database/database_adapter.d.ts +116 -7
  35. package/dist/database/database_adapter.d.ts.map +1 -1
  36. package/dist/database/database_adapter.js +41 -1
  37. package/dist/database/database_adapter.js.map +1 -1
  38. package/dist/engine/digital_twin_engine.d.ts +12 -0
  39. package/dist/engine/digital_twin_engine.d.ts.map +1 -1
  40. package/dist/engine/digital_twin_engine.js +15 -9
  41. package/dist/engine/digital_twin_engine.js.map +1 -1
  42. package/dist/engine/initializer.d.ts +8 -5
  43. package/dist/engine/initializer.d.ts.map +1 -1
  44. package/dist/engine/initializer.js +32 -13
  45. package/dist/engine/initializer.js.map +1 -1
  46. package/dist/index.d.ts +1 -1
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js.map +1 -1
  49. package/dist/types/data_record.d.ts +8 -0
  50. package/dist/types/data_record.d.ts.map +1 -1
  51. package/dist/utils/map_to_data_record.d.ts.map +1 -1
  52. package/dist/utils/map_to_data_record.js +4 -1
  53. package/dist/utils/map_to_data_record.js.map +1 -1
  54. package/package.json +1 -1
@@ -132,6 +132,51 @@ export class AssetsManager {
132
132
  // Check if the filename ends with the required extension (case-insensitive)
133
133
  return filename.toLowerCase().endsWith(requiredExtension.toLowerCase());
134
134
  }
135
+ /**
136
+ * Validates that a string is valid base64-encoded data.
137
+ *
138
+ * Used internally to ensure file data in batch uploads is properly base64-encoded
139
+ * before attempting to decode it.
140
+ *
141
+ * @private
142
+ * @param {any} data - Data to validate as base64
143
+ * @returns {boolean} True if data is a valid base64 string, false otherwise
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * this.validateBase64('SGVsbG8gV29ybGQ=') // returns true
148
+ * this.validateBase64('not-base64!@#') // returns false
149
+ * this.validateBase64(123) // returns false (not a string)
150
+ * this.validateBase64('') // returns false (empty string)
151
+ * ```
152
+ */
153
+ validateBase64(data) {
154
+ // Must be a non-empty string
155
+ if (typeof data !== 'string' || data.length === 0) {
156
+ return false;
157
+ }
158
+ // Base64 regex: only A-Z, a-z, 0-9, +, /, and = for padding
159
+ // Padding (=) can only appear at the end, max 2 times
160
+ const base64Regex = /^[A-Za-z0-9+/]+={0,2}$/;
161
+ const trimmed = data.trim();
162
+ // Must match regex
163
+ if (!base64Regex.test(trimmed)) {
164
+ return false;
165
+ }
166
+ // Length must be multiple of 4
167
+ if (trimmed.length % 4 !== 0) {
168
+ return false;
169
+ }
170
+ // Try to decode to verify it's valid base64
171
+ try {
172
+ const decoded = Buffer.from(trimmed, 'base64').toString('base64');
173
+ // Re-encode and compare to ensure no data loss (valid base64)
174
+ return decoded === trimmed;
175
+ }
176
+ catch {
177
+ return false;
178
+ }
179
+ }
135
180
  /**
136
181
  * Upload a new asset file with metadata.
137
182
  *
@@ -148,7 +193,8 @@ export class AssetsManager {
148
193
  * source: 'https://city-data.example.com/buildings',
149
194
  * owner_id: 'user123',
150
195
  * filename: 'building.glb',
151
- * file: fileBuffer
196
+ * file: fileBuffer,
197
+ * is_public: true
152
198
  * })
153
199
  * ```
154
200
  */
@@ -173,7 +219,8 @@ export class AssetsManager {
173
219
  description: request.description,
174
220
  source: request.source,
175
221
  owner_id: request.owner_id,
176
- filename: request.filename
222
+ filename: request.filename,
223
+ is_public: request.is_public ?? true // Default to public if not specified
177
224
  };
178
225
  await this.db.save(metadata);
179
226
  }
@@ -183,15 +230,61 @@ export class AssetsManager {
183
230
  * Returns a JSON list of all assets with their metadata, following the
184
231
  * framework pattern but adapted for assets management.
185
232
  *
233
+ * Access control:
234
+ * - Unauthenticated users: Can only see public assets
235
+ * - Authenticated users: Can see public assets + their own private assets
236
+ * - Admin users: Can see all assets (public and private from all users)
237
+ *
186
238
  * @returns {Promise<DataResponse>} JSON response with all assets
187
239
  */
188
- async retrieve() {
240
+ async retrieve(req) {
189
241
  try {
190
242
  const assets = await this.getAllAssets();
191
243
  const config = this.getConfiguration();
244
+ // Check if user is admin (admins can see everything)
245
+ const isAdmin = req && ApisixAuthParser.isAdmin(req.headers || {});
246
+ // If admin, skip filtering and return all assets
247
+ if (isAdmin) {
248
+ const assetsWithMetadata = assets.map(asset => ({
249
+ id: asset.id,
250
+ name: asset.name,
251
+ date: asset.date,
252
+ contentType: asset.contentType,
253
+ description: asset.description || '',
254
+ source: asset.source || '',
255
+ owner_id: asset.owner_id || null,
256
+ filename: asset.filename || '',
257
+ is_public: asset.is_public ?? true,
258
+ url: `/${config.name}/${asset.id}`,
259
+ download_url: `/${config.name}/${asset.id}/download`
260
+ }));
261
+ return {
262
+ status: 200,
263
+ content: JSON.stringify(assetsWithMetadata),
264
+ headers: { 'Content-Type': 'application/json' }
265
+ };
266
+ }
267
+ // For non-admin users, check authentication and ownership
268
+ let authenticatedUserId = null;
269
+ if (req && ApisixAuthParser.hasValidAuth(req.headers || {})) {
270
+ const authUser = ApisixAuthParser.parseAuthHeaders(req.headers || {});
271
+ if (authUser) {
272
+ const userRecord = await this.userService.findOrCreateUser(authUser);
273
+ authenticatedUserId = userRecord.id || null;
274
+ }
275
+ }
276
+ // Filter assets based on visibility and ownership
277
+ const visibleAssets = assets.filter(asset => {
278
+ // Public assets are visible to everyone
279
+ if (asset.is_public) {
280
+ return true;
281
+ }
282
+ // Private assets are only visible to the owner
283
+ return authenticatedUserId !== null && asset.owner_id === authenticatedUserId;
284
+ });
192
285
  // Transform to include asset metadata and user-friendly URLs
193
286
  // Note: assets are already sorted by date desc from DB query
194
- const assetsWithMetadata = assets.map(asset => ({
287
+ const assetsWithMetadata = visibleAssets.map(asset => ({
195
288
  id: asset.id,
196
289
  name: asset.name,
197
290
  date: asset.date,
@@ -201,6 +294,7 @@ export class AssetsManager {
201
294
  source: asset.source || '',
202
295
  owner_id: asset.owner_id || null,
203
296
  filename: asset.filename || '',
297
+ is_public: asset.is_public ?? true,
204
298
  // Add URLs that the front-end can actually use
205
299
  url: `/${config.name}/${asset.id}`, // For display/use
206
300
  download_url: `/${config.name}/${asset.id}/download` // For download
@@ -264,7 +358,7 @@ export class AssetsManager {
264
358
  /**
265
359
  * Update asset metadata by ID.
266
360
  *
267
- * Updates the metadata (description and/or source) of a specific asset.
361
+ * Updates the metadata (description, source, and/or visibility) of a specific asset.
268
362
  * Asset metadata is stored as dedicated columns in the database.
269
363
  *
270
364
  * @param {string} id - The ID of the asset to update
@@ -275,7 +369,8 @@ export class AssetsManager {
275
369
  * ```typescript
276
370
  * await assetsManager.updateAssetMetadata('123', {
277
371
  * description: 'Updated building model with new textures',
278
- * source: 'https://updated-source.example.com'
372
+ * source: 'https://updated-source.example.com',
373
+ * is_public: false
279
374
  * })
280
375
  * ```
281
376
  */
@@ -302,7 +397,8 @@ export class AssetsManager {
302
397
  description: updates.description ?? record.description ?? '',
303
398
  source: updates.source ?? record.source ?? '',
304
399
  owner_id: record.owner_id ?? null,
305
- filename: record.filename ?? ''
400
+ filename: record.filename ?? '',
401
+ is_public: updates.is_public !== undefined ? updates.is_public : (record.is_public ?? true)
306
402
  };
307
403
  // Update the record - delete and re-create with updated metadata
308
404
  await this.db.delete(id, this.getConfiguration().name);
@@ -393,7 +489,8 @@ export class AssetsManager {
393
489
  description: request.description,
394
490
  source: request.source,
395
491
  owner_id: request.owner_id,
396
- filename: request.filename
492
+ filename: request.filename,
493
+ is_public: request.is_public ?? true
397
494
  };
398
495
  metadataList.push(metadata);
399
496
  }
@@ -491,10 +588,37 @@ export class AssetsManager {
491
588
  ];
492
589
  }
493
590
  /**
494
- * Handle upload endpoint
591
+ * Handle single asset upload via HTTP POST.
592
+ *
593
+ * Flow:
594
+ * 1. Validate request structure and authentication
595
+ * 2. Extract user identity from Apache APISIX headers
596
+ * 3. Validate file extension and read uploaded file
597
+ * 4. Store file via storage service and metadata in database
598
+ * 5. Set owner_id to authenticated user (prevents ownership spoofing)
599
+ * 6. Apply is_public setting (defaults to true if not specified)
600
+ *
601
+ * Authentication: Required
602
+ * Ownership: Automatically set to authenticated user
603
+ *
604
+ * @param req - HTTP request with multipart/form-data file upload
605
+ * @returns HTTP response with success/error status
606
+ *
607
+ * @example
608
+ * POST /assets
609
+ * Content-Type: multipart/form-data
610
+ * x-user-id: user-uuid
611
+ * x-user-roles: user,premium
612
+ *
613
+ * Form data:
614
+ * - file: <binary file>
615
+ * - description: "3D model of building"
616
+ * - source: "https://source.com"
617
+ * - is_public: true
495
618
  */
496
619
  async handleUpload(req) {
497
620
  try {
621
+ // Validate request structure
498
622
  if (!req || !req.body) {
499
623
  return {
500
624
  status: 400,
@@ -504,7 +628,7 @@ export class AssetsManager {
504
628
  headers: { 'Content-Type': 'application/json' }
505
629
  };
506
630
  }
507
- // Check authentication
631
+ // Step 1: Verify user is authenticated via Apache APISIX headers
508
632
  if (!ApisixAuthParser.hasValidAuth(req.headers || {})) {
509
633
  return {
510
634
  status: 401,
@@ -514,7 +638,7 @@ export class AssetsManager {
514
638
  headers: { 'Content-Type': 'application/json' }
515
639
  };
516
640
  }
517
- // Parse authenticated user
641
+ // Step 2: Extract user identity from APISIX headers (x-user-id, x-user-roles)
518
642
  const authUser = ApisixAuthParser.parseAuthHeaders(req.headers || {});
519
643
  if (!authUser) {
520
644
  return {
@@ -525,11 +649,22 @@ export class AssetsManager {
525
649
  headers: { 'Content-Type': 'application/json' }
526
650
  };
527
651
  }
528
- // Find or create user in database
652
+ // Step 3: Find or create user record in database and sync roles from Keycloak
529
653
  const userRecord = await this.userService.findOrCreateUser(authUser);
530
- const { description, source } = req.body;
654
+ if (!userRecord.id) {
655
+ return {
656
+ status: 500,
657
+ content: JSON.stringify({
658
+ error: 'Failed to retrieve user information'
659
+ }),
660
+ headers: { 'Content-Type': 'application/json' }
661
+ };
662
+ }
663
+ // Step 4: Extract form data from multipart upload
664
+ const { description, source, is_public } = req.body;
531
665
  const filePath = req.file?.path;
532
666
  const filename = req.file?.originalname || req.body.filename;
667
+ // Step 5: Validate required fields are present
533
668
  if (!filePath || !description || !source) {
534
669
  return {
535
670
  status: 400,
@@ -579,7 +714,8 @@ export class AssetsManager {
579
714
  source,
580
715
  owner_id: userRecord.id,
581
716
  filename,
582
- file: fileBuffer
717
+ file: fileBuffer,
718
+ is_public: is_public !== undefined ? Boolean(is_public) : true
583
719
  });
584
720
  // Clean up temporary file after successful upload
585
721
  await fs.unlink(filePath).catch(() => {
@@ -654,7 +790,7 @@ export class AssetsManager {
654
790
  };
655
791
  }
656
792
  const { id } = req.params || {};
657
- const { description, source } = req.body || {};
793
+ const { description, source, is_public } = req.body || {};
658
794
  if (!id) {
659
795
  return {
660
796
  status: 400,
@@ -664,17 +800,26 @@ export class AssetsManager {
664
800
  headers: { 'Content-Type': 'application/json' }
665
801
  };
666
802
  }
667
- if (!description && !source) {
803
+ if (!description && !source && is_public === undefined) {
668
804
  return {
669
805
  status: 400,
670
806
  content: JSON.stringify({
671
- error: 'At least one field (description or source) must be provided for update'
807
+ error: 'At least one field (description, source, or is_public) must be provided for update'
672
808
  }),
673
809
  headers: { 'Content-Type': 'application/json' }
674
810
  };
675
811
  }
676
812
  // Find or create user in database
677
813
  const userRecord = await this.userService.findOrCreateUser(authUser);
814
+ if (!userRecord.id) {
815
+ return {
816
+ status: 500,
817
+ content: JSON.stringify({
818
+ error: 'Failed to retrieve user information'
819
+ }),
820
+ headers: { 'Content-Type': 'application/json' }
821
+ };
822
+ }
678
823
  // Check if asset exists and belongs to this user
679
824
  const asset = await this.getAssetById(id);
680
825
  if (!asset) {
@@ -701,6 +846,8 @@ export class AssetsManager {
701
846
  updates.description = description;
702
847
  if (source !== undefined)
703
848
  updates.source = source;
849
+ if (is_public !== undefined)
850
+ updates.is_public = Boolean(is_public);
704
851
  await this.updateAssetMetadata(id, updates);
705
852
  return {
706
853
  status: 200,
@@ -724,6 +871,11 @@ export class AssetsManager {
724
871
  * Returns the file content of a specific asset by ID for display/use in front-end.
725
872
  * No download headers - just the raw file content.
726
873
  *
874
+ * Access control:
875
+ * - Public assets: Accessible to everyone
876
+ * - Private assets: Accessible only to owner
877
+ * - Admin users: Can access all assets (public and private)
878
+ *
727
879
  * @param {any} req - HTTP request object with params.id
728
880
  * @returns {Promise<DataResponse>} HTTP response with file content
729
881
  *
@@ -762,6 +914,42 @@ export class AssetsManager {
762
914
  headers: { 'Content-Type': 'text/plain' }
763
915
  };
764
916
  }
917
+ // Check if user is admin (admins can access everything)
918
+ const isAdmin = ApisixAuthParser.isAdmin(req.headers || {});
919
+ // Check visibility: if private and user is not admin, verify ownership
920
+ if (!asset.is_public && !isAdmin) {
921
+ // Asset is private, require authentication
922
+ if (!ApisixAuthParser.hasValidAuth(req.headers || {})) {
923
+ return {
924
+ status: 401,
925
+ content: JSON.stringify({
926
+ error: 'Authentication required for private assets'
927
+ }),
928
+ headers: { 'Content-Type': 'application/json' }
929
+ };
930
+ }
931
+ // Verify ownership
932
+ const authUser = ApisixAuthParser.parseAuthHeaders(req.headers || {});
933
+ if (!authUser) {
934
+ return {
935
+ status: 401,
936
+ content: JSON.stringify({
937
+ error: 'Invalid authentication headers'
938
+ }),
939
+ headers: { 'Content-Type': 'application/json' }
940
+ };
941
+ }
942
+ const userRecord = await this.userService.findOrCreateUser(authUser);
943
+ if (!userRecord.id || asset.owner_id !== userRecord.id) {
944
+ return {
945
+ status: 403,
946
+ content: JSON.stringify({
947
+ error: 'This asset is private'
948
+ }),
949
+ headers: { 'Content-Type': 'application/json' }
950
+ };
951
+ }
952
+ }
765
953
  // Get the file content
766
954
  const fileContent = await asset.data();
767
955
  return {
@@ -789,6 +977,11 @@ export class AssetsManager {
789
977
  * Downloads the file content of a specific asset by ID with download headers.
790
978
  * Forces browser to download the file rather than display it.
791
979
  *
980
+ * Access control:
981
+ * - Public assets: Accessible to everyone
982
+ * - Private assets: Accessible only to owner
983
+ * - Admin users: Can download all assets (public and private)
984
+ *
792
985
  * @param {any} req - HTTP request object with params.id
793
986
  * @returns {Promise<DataResponse>} HTTP response with file content and download headers
794
987
  *
@@ -827,6 +1020,42 @@ export class AssetsManager {
827
1020
  headers: { 'Content-Type': 'text/plain' }
828
1021
  };
829
1022
  }
1023
+ // Check if user is admin (admins can download everything)
1024
+ const isAdmin = ApisixAuthParser.isAdmin(req.headers || {});
1025
+ // Check visibility: if private and user is not admin, verify ownership
1026
+ if (!asset.is_public && !isAdmin) {
1027
+ // Asset is private, require authentication
1028
+ if (!ApisixAuthParser.hasValidAuth(req.headers || {})) {
1029
+ return {
1030
+ status: 401,
1031
+ content: JSON.stringify({
1032
+ error: 'Authentication required for private assets'
1033
+ }),
1034
+ headers: { 'Content-Type': 'application/json' }
1035
+ };
1036
+ }
1037
+ // Verify ownership
1038
+ const authUser = ApisixAuthParser.parseAuthHeaders(req.headers || {});
1039
+ if (!authUser) {
1040
+ return {
1041
+ status: 401,
1042
+ content: JSON.stringify({
1043
+ error: 'Invalid authentication headers'
1044
+ }),
1045
+ headers: { 'Content-Type': 'application/json' }
1046
+ };
1047
+ }
1048
+ const userRecord = await this.userService.findOrCreateUser(authUser);
1049
+ if (!userRecord.id || asset.owner_id !== userRecord.id) {
1050
+ return {
1051
+ status: 403,
1052
+ content: JSON.stringify({
1053
+ error: 'This asset is private'
1054
+ }),
1055
+ headers: { 'Content-Type': 'application/json' }
1056
+ };
1057
+ }
1058
+ }
830
1059
  // Get the file content
831
1060
  const fileContent = await asset.data();
832
1061
  // Get original filename from asset metadata
@@ -898,6 +1127,15 @@ export class AssetsManager {
898
1127
  }
899
1128
  // Find or create user in database
900
1129
  const userRecord = await this.userService.findOrCreateUser(authUser);
1130
+ if (!userRecord.id) {
1131
+ return {
1132
+ status: 500,
1133
+ content: JSON.stringify({
1134
+ error: 'Failed to retrieve user information'
1135
+ }),
1136
+ headers: { 'Content-Type': 'application/json' }
1137
+ };
1138
+ }
901
1139
  // Check if asset exists and belongs to this user
902
1140
  const asset = await this.getAssetById(id);
903
1141
  if (!asset) {
@@ -950,6 +1188,38 @@ export class AssetsManager {
950
1188
  headers: { 'Content-Type': 'application/json' }
951
1189
  };
952
1190
  }
1191
+ // Check authentication
1192
+ if (!ApisixAuthParser.hasValidAuth(req.headers || {})) {
1193
+ return {
1194
+ status: 401,
1195
+ content: JSON.stringify({
1196
+ error: 'Authentication required'
1197
+ }),
1198
+ headers: { 'Content-Type': 'application/json' }
1199
+ };
1200
+ }
1201
+ // Parse authenticated user
1202
+ const authUser = ApisixAuthParser.parseAuthHeaders(req.headers || {});
1203
+ if (!authUser) {
1204
+ return {
1205
+ status: 401,
1206
+ content: JSON.stringify({
1207
+ error: 'Invalid authentication headers'
1208
+ }),
1209
+ headers: { 'Content-Type': 'application/json' }
1210
+ };
1211
+ }
1212
+ // Find or create user in database
1213
+ const userRecord = await this.userService.findOrCreateUser(authUser);
1214
+ if (!userRecord.id) {
1215
+ return {
1216
+ status: 500,
1217
+ content: JSON.stringify({
1218
+ error: 'Failed to retrieve user information'
1219
+ }),
1220
+ headers: { 'Content-Type': 'application/json' }
1221
+ };
1222
+ }
953
1223
  const requests = req.body.requests;
954
1224
  if (!Array.isArray(requests) || requests.length === 0) {
955
1225
  return {
@@ -960,7 +1230,7 @@ export class AssetsManager {
960
1230
  headers: { 'Content-Type': 'application/json' }
961
1231
  };
962
1232
  }
963
- // Validate all requests first (including extensions)
1233
+ // Validate all requests first (including extensions and base64 format)
964
1234
  for (const request of requests) {
965
1235
  if (!request.file || !request.description || !request.source || !request.filename) {
966
1236
  return {
@@ -971,6 +1241,16 @@ export class AssetsManager {
971
1241
  headers: { 'Content-Type': 'application/json' }
972
1242
  };
973
1243
  }
1244
+ // Validate that file is valid base64 string
1245
+ if (!this.validateBase64(request.file)) {
1246
+ return {
1247
+ status: 400,
1248
+ content: JSON.stringify({
1249
+ error: `Invalid base64 data for file: ${request.filename}. File must be a valid base64-encoded string.`
1250
+ }),
1251
+ headers: { 'Content-Type': 'application/json' }
1252
+ };
1253
+ }
974
1254
  // Validate file extension for each request
975
1255
  if (!this.validateFileExtension(request.filename)) {
976
1256
  const config = this.getConfiguration();
@@ -990,9 +1270,10 @@ export class AssetsManager {
990
1270
  await this.uploadAsset({
991
1271
  description: request.description,
992
1272
  source: request.source,
993
- owner_id: request.owner_id || null,
1273
+ owner_id: userRecord.id,
994
1274
  filename: request.filename,
995
- file: Buffer.from(request.file, 'base64') // Assuming base64 encoded
1275
+ file: Buffer.from(request.file, 'base64'), // Already validated as base64 above
1276
+ is_public: request.is_public !== undefined ? Boolean(request.is_public) : true
996
1277
  });
997
1278
  results.push({ success: true, filename: request.filename });
998
1279
  }
@@ -1039,6 +1320,38 @@ export class AssetsManager {
1039
1320
  headers: { 'Content-Type': 'application/json' }
1040
1321
  };
1041
1322
  }
1323
+ // Check authentication
1324
+ if (!ApisixAuthParser.hasValidAuth(req.headers || {})) {
1325
+ return {
1326
+ status: 401,
1327
+ content: JSON.stringify({
1328
+ error: 'Authentication required'
1329
+ }),
1330
+ headers: { 'Content-Type': 'application/json' }
1331
+ };
1332
+ }
1333
+ // Parse authenticated user
1334
+ const authUser = ApisixAuthParser.parseAuthHeaders(req.headers || {});
1335
+ if (!authUser) {
1336
+ return {
1337
+ status: 401,
1338
+ content: JSON.stringify({
1339
+ error: 'Invalid authentication headers'
1340
+ }),
1341
+ headers: { 'Content-Type': 'application/json' }
1342
+ };
1343
+ }
1344
+ // Find or create user in database
1345
+ const userRecord = await this.userService.findOrCreateUser(authUser);
1346
+ if (!userRecord.id) {
1347
+ return {
1348
+ status: 500,
1349
+ content: JSON.stringify({
1350
+ error: 'Failed to retrieve user information'
1351
+ }),
1352
+ headers: { 'Content-Type': 'application/json' }
1353
+ };
1354
+ }
1042
1355
  const { ids } = req.body;
1043
1356
  if (!Array.isArray(ids) || ids.length === 0) {
1044
1357
  return {
@@ -1049,10 +1362,29 @@ export class AssetsManager {
1049
1362
  headers: { 'Content-Type': 'application/json' }
1050
1363
  };
1051
1364
  }
1052
- // Process deletions individually with error handling
1365
+ // Process deletions individually with error handling and ownership checks
1053
1366
  const results = [];
1054
1367
  for (const id of ids) {
1055
1368
  try {
1369
+ // Check if asset exists and belongs to this user
1370
+ const asset = await this.getAssetById(id);
1371
+ if (!asset) {
1372
+ results.push({
1373
+ success: false,
1374
+ id,
1375
+ error: 'Asset not found'
1376
+ });
1377
+ continue;
1378
+ }
1379
+ // Check ownership (only owner can delete their assets)
1380
+ if (asset.owner_id !== userRecord.id) {
1381
+ results.push({
1382
+ success: false,
1383
+ id,
1384
+ error: 'You can only delete your own assets'
1385
+ });
1386
+ continue;
1387
+ }
1056
1388
  await this.deleteAssetById(id);
1057
1389
  results.push({ success: true, id });
1058
1390
  }