@cytario/web 2.1.8 → 2.2.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 (72) hide show
  1. package/README.md +1 -1
  2. package/app/.server/auth/README.md +0 -1
  3. package/app/.server/auth/authMiddleware.ts +6 -28
  4. package/app/.server/auth/getSessionCredentials.ts +11 -1
  5. package/app/.server/auth/sessionPolicy.ts +69 -0
  6. package/app/.server/corsPreflight.ts +136 -0
  7. package/app/.server/csp.ts +34 -0
  8. package/app/components/.client/ImageViewer/README.md +2 -2
  9. package/app/components/DirectoryView/DirectoryViewGrid.tsx +2 -11
  10. package/app/components/DirectoryView/DirectoryViewTree.tsx +18 -26
  11. package/app/components/DirectoryView/buildDirectoryTree.ts +89 -42
  12. package/app/components/DirectoryView/filterNodes.ts +4 -19
  13. package/app/components/DirectoryView/useLazyTreeNodes.ts +120 -0
  14. package/app/components/GlobalSearch/GlobalSearch.tsx +8 -3
  15. package/app/components/GlobalSearch/Suggestions.tsx +21 -3
  16. package/app/config.ts +0 -7
  17. package/app/entry.server.tsx +9 -4
  18. package/app/hooks/useInitConnections.ts +3 -3
  19. package/app/root.tsx +11 -6
  20. package/app/routes/connections/connection.form.tsx +9 -3
  21. package/app/routes/connections/connection.schema.ts +60 -11
  22. package/app/routes/connections/connections.clientLoader.ts +91 -0
  23. package/app/routes/connections/connections.loader.ts +19 -39
  24. package/app/routes/connections/connections.route.tsx +5 -0
  25. package/app/routes/connections/createConnection.action.ts +39 -13
  26. package/app/routes/connections/deleteConnection.action.ts +5 -1
  27. package/app/routes/connections/updateConnection.action.ts +50 -13
  28. package/app/routes/home/home.route.tsx +5 -1
  29. package/app/routes/layouts/protected.layout.tsx +15 -1
  30. package/app/routes/objects/objects.clientLoader.ts +73 -0
  31. package/app/routes/objects/objects.loader.ts +58 -92
  32. package/app/routes/objects/objects.route.tsx +41 -40
  33. package/app/routes/search.route.tsx +133 -28
  34. package/app/routes.ts +0 -4
  35. package/app/utils/connectionsStore/useConnectionsStore.ts +25 -62
  36. package/app/utils/credentialsRefresh.ts +53 -0
  37. package/app/utils/db/convertCsvToParquet.ts +24 -16
  38. package/app/utils/db/createDatabase.ts +25 -33
  39. package/app/utils/db/duckdbBundles.ts +42 -0
  40. package/app/utils/db/ensureSpatialLoaded.ts +28 -0
  41. package/app/utils/db/escapeSqlString.ts +4 -0
  42. package/app/utils/db/getBlobFromObjectNode.ts +15 -36
  43. package/app/utils/db/getTileDataWasm.ts +4 -5
  44. package/app/utils/db/sqlQueries.ts +6 -1
  45. package/app/utils/filterObjects.ts +2 -18
  46. package/app/utils/limitConcurrency.ts +28 -0
  47. package/app/utils/listObjectsClient.ts +318 -0
  48. package/app/utils/listingLimits.ts +7 -0
  49. package/app/utils/loadConnectionLevel.ts +50 -0
  50. package/app/utils/localFilesStore/useFileStore.ts +1 -6
  51. package/app/utils/pathUtils.ts +65 -0
  52. package/app/utils/resourceId.ts +14 -29
  53. package/app/utils/s3HostAllowlist.ts +116 -0
  54. package/app/utils/signedFetch.ts +159 -24
  55. package/package.json +3 -3
  56. package/prisma/seed.ts +3 -3
  57. package/public/duckdb-extensions/checksums.json +12 -0
  58. package/public/duckdb-extensions/v1.4.3/wasm_eh/httpfs.duckdb_extension.wasm +0 -0
  59. package/public/duckdb-extensions/v1.4.3/wasm_eh/parquet.duckdb_extension.wasm +0 -0
  60. package/public/duckdb-extensions/v1.4.3/wasm_eh/spatial.duckdb_extension.wasm +0 -0
  61. package/public/duckdb-extensions/v1.4.3/wasm_mvp/httpfs.duckdb_extension.wasm +0 -0
  62. package/public/duckdb-extensions/v1.4.3/wasm_mvp/parquet.duckdb_extension.wasm +0 -0
  63. package/public/duckdb-extensions/v1.4.3/wasm_mvp/spatial.duckdb_extension.wasm +0 -0
  64. package/public/duckdb-extensions/v1.4.3/wasm_threads/httpfs.duckdb_extension.wasm +0 -0
  65. package/public/duckdb-extensions/v1.4.3/wasm_threads/parquet.duckdb_extension.wasm +0 -0
  66. package/public/duckdb-extensions/v1.4.3/wasm_threads/spatial.duckdb_extension.wasm +0 -0
  67. package/scripts/download-duckdb-extensions.mjs +188 -0
  68. package/scripts/prebuild.mjs +18 -10
  69. package/app/.server/auth/getPresignedUrl.ts +0 -21
  70. package/app/.server/auth/getS3Client.ts +0 -93
  71. package/app/routes/presign.route.tsx +0 -42
  72. package/app/utils/getObjects.ts +0 -24
@@ -1,93 +0,0 @@
1
- import { S3Client } from "@aws-sdk/client-s3";
2
- import { Credentials } from "@aws-sdk/client-sts";
3
- import crypto from "crypto";
4
- import { LRUCache } from "lru-cache";
5
-
6
- import { ConnectionConfig } from "~/.generated/client";
7
- import { isAwsS3Endpoint } from "~/utils/s3Provider";
8
-
9
- interface CacheEntry {
10
- client: S3Client;
11
- userId: string;
12
- bucketName: string;
13
- }
14
-
15
- /**
16
- * User-scoped S3Client cache with TTL and LRU eviction.
17
- * Prevents cross-user data leakage and handles credential expiration.
18
- */
19
- const s3ClientCache = new LRUCache<string, CacheEntry>({
20
- max: 10000,
21
- ttl: 3600000, // 1 hour in milliseconds (matches typical STS credential TTL)
22
- });
23
-
24
- /**
25
- * Creates a unique cache key scoped to user, bucket, and credential identity.
26
- */
27
- const createCacheKey = (userId: string, bucketName: string, credentials: Credentials): string => {
28
- // Hash credentials to detect when they change (e.g., after refresh)
29
- const credHash = crypto
30
- .createHash("sha256")
31
- .update(`${credentials.AccessKeyId}:${credentials.SecretAccessKey}:${credentials.SessionToken}`)
32
- .digest("hex")
33
- .substring(0, 16);
34
-
35
- return `${userId}:${bucketName}:${credHash}`;
36
- };
37
-
38
- export const getS3Client = async (
39
- connectionConfig: ConnectionConfig,
40
- credentials: Credentials,
41
- userId: string,
42
- ): Promise<S3Client> => {
43
- const { bucketName, region, endpoint } = connectionConfig;
44
- const { AccessKeyId, SecretAccessKey, SessionToken } = credentials;
45
-
46
- if (!AccessKeyId || !SecretAccessKey) throw Error("No Credentials");
47
- if (!userId) throw Error("User ID is required for S3Client cache");
48
-
49
- // Check cache first
50
- const key = createCacheKey(userId, bucketName, credentials);
51
- const cachedEntry = s3ClientCache.get(key);
52
- if (cachedEntry) {
53
- return cachedEntry.client;
54
- }
55
-
56
- // Use default region if null
57
- const actualRegion = region ?? "eu-central-1";
58
-
59
- // Detect if this is AWS S3 or a compatible service (MinIO, etc.)
60
- const isAwsS3 = isAwsS3Endpoint(endpoint);
61
-
62
- const s3Client = new S3Client({
63
- region: actualRegion,
64
- // Only set endpoint for non-AWS S3 services
65
- ...(endpoint && !isAwsS3 ? { endpoint } : {}),
66
- credentials: {
67
- accessKeyId: AccessKeyId,
68
- secretAccessKey: SecretAccessKey,
69
- sessionToken: SessionToken,
70
- },
71
- // Only use path style for non-AWS S3 services (MinIO, etc.)
72
- forcePathStyle: !isAwsS3,
73
- });
74
-
75
- // Cache the client
76
- s3ClientCache.set(key, {
77
- client: s3Client,
78
- userId,
79
- bucketName,
80
- });
81
-
82
- return s3Client;
83
- };
84
-
85
- /** Remove all cached S3 clients for a given user + bucket (e.g. after config change). */
86
- export const invalidateS3ClientsForBucket = (userId: string, bucketName: string): void => {
87
- for (const key of s3ClientCache.keys()) {
88
- const entry = s3ClientCache.peek(key);
89
- if (entry && entry.userId === userId && entry.bucketName === bucketName) {
90
- s3ClientCache.delete(key);
91
- }
92
- }
93
- };
@@ -1,42 +0,0 @@
1
- import { ActionFunctionArgs } from "react-router";
2
-
3
- import { authContext, authMiddleware } from "~/.server/auth/authMiddleware";
4
- import { getPresignedUrl } from "~/.server/auth/getPresignedUrl";
5
- import { getS3Client } from "~/.server/auth/getS3Client";
6
- import { createLabel } from "~/.server/logging";
7
- import { requestDurationMiddleware } from "~/.server/requestDurationMiddleware";
8
- import { getConnection } from "~/routes/connections/connections.server";
9
-
10
- export const middleware = [requestDurationMiddleware, authMiddleware];
11
-
12
- const label = createLabel("presign", "gray");
13
-
14
- export const loader = async ({ params, context }: ActionFunctionArgs): Promise<Response> => {
15
- const { user, credentials: connectionsCredentials } = context.get(authContext);
16
- const { name: connectionName } = params;
17
- const pathName = params["*"] ?? "";
18
-
19
- if (!connectionName) throw new Error("Connection name is required");
20
-
21
- const connectionConfig = await getConnection(user, connectionName);
22
- if (!connectionConfig) {
23
- throw new Error("Connection configuration not found");
24
- }
25
-
26
- const { provider, bucketName, prefix: connPrefix } = connectionConfig;
27
- const s3Key = connPrefix ? `${connPrefix.replace(/\/$/, "")}/${pathName}` : pathName;
28
- console.info(`${label} Presign route: ${provider}/${bucketName}/${s3Key}`);
29
-
30
- const credentials = connectionsCredentials[connectionName];
31
- if (!credentials) throw new Error(`No credentials for connection: ${connectionName}`);
32
-
33
- try {
34
- const s3Client = await getS3Client(connectionConfig, credentials, user.sub);
35
- const presignedUrl = await getPresignedUrl(connectionConfig, s3Client, s3Key);
36
-
37
- return Response.json({ url: presignedUrl });
38
- } catch (error) {
39
- console.error("Error presigning url", error);
40
- return Response.json({ error: "Failed to generate presigned URL" }, { status: 500 });
41
- }
42
- };
@@ -1,24 +0,0 @@
1
- import { _Object, ListObjectsV2Command, S3Client } from "@aws-sdk/client-s3";
2
-
3
- import { filterObjects } from "./filterObjects";
4
- import { ConnectionConfig } from "~/.generated/client";
5
-
6
- export const getObjects = async (
7
- connectionConfig: ConnectionConfig,
8
- s3Client: S3Client,
9
- query?: string | null,
10
- prefix?: string,
11
- maxKeys?: number,
12
- ): Promise<_Object[]> => {
13
- const listObjectsCommand = new ListObjectsV2Command({
14
- Bucket: connectionConfig.bucketName,
15
- Prefix: prefix,
16
- ...(maxKeys ? { MaxKeys: maxKeys } : {}),
17
- });
18
-
19
- const { Contents } = await s3Client.send(listObjectsCommand);
20
-
21
- const filteredFiles = filterObjects(Contents, { query });
22
-
23
- return filteredFiles;
24
- };