@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.
- package/README.md +1 -1
- package/app/.server/auth/README.md +0 -1
- package/app/.server/auth/authMiddleware.ts +6 -28
- package/app/.server/auth/getSessionCredentials.ts +11 -1
- package/app/.server/auth/sessionPolicy.ts +69 -0
- package/app/.server/corsPreflight.ts +136 -0
- package/app/.server/csp.ts +34 -0
- package/app/components/.client/ImageViewer/README.md +2 -2
- package/app/components/DirectoryView/DirectoryViewGrid.tsx +2 -11
- package/app/components/DirectoryView/DirectoryViewTree.tsx +18 -26
- package/app/components/DirectoryView/buildDirectoryTree.ts +89 -42
- package/app/components/DirectoryView/filterNodes.ts +4 -19
- package/app/components/DirectoryView/useLazyTreeNodes.ts +120 -0
- package/app/components/GlobalSearch/GlobalSearch.tsx +8 -3
- package/app/components/GlobalSearch/Suggestions.tsx +21 -3
- package/app/config.ts +0 -7
- package/app/entry.server.tsx +9 -4
- package/app/hooks/useInitConnections.ts +3 -3
- package/app/root.tsx +11 -6
- package/app/routes/connections/connection.form.tsx +9 -3
- package/app/routes/connections/connection.schema.ts +60 -11
- package/app/routes/connections/connections.clientLoader.ts +91 -0
- package/app/routes/connections/connections.loader.ts +19 -39
- package/app/routes/connections/connections.route.tsx +5 -0
- package/app/routes/connections/createConnection.action.ts +39 -13
- package/app/routes/connections/deleteConnection.action.ts +5 -1
- package/app/routes/connections/updateConnection.action.ts +50 -13
- package/app/routes/home/home.route.tsx +5 -1
- package/app/routes/layouts/protected.layout.tsx +15 -1
- package/app/routes/objects/objects.clientLoader.ts +73 -0
- package/app/routes/objects/objects.loader.ts +58 -92
- package/app/routes/objects/objects.route.tsx +41 -40
- package/app/routes/search.route.tsx +133 -28
- package/app/routes.ts +0 -4
- package/app/utils/connectionsStore/useConnectionsStore.ts +25 -62
- package/app/utils/credentialsRefresh.ts +53 -0
- package/app/utils/db/convertCsvToParquet.ts +24 -16
- package/app/utils/db/createDatabase.ts +25 -33
- package/app/utils/db/duckdbBundles.ts +42 -0
- package/app/utils/db/ensureSpatialLoaded.ts +28 -0
- package/app/utils/db/escapeSqlString.ts +4 -0
- package/app/utils/db/getBlobFromObjectNode.ts +15 -36
- package/app/utils/db/getTileDataWasm.ts +4 -5
- package/app/utils/db/sqlQueries.ts +6 -1
- package/app/utils/filterObjects.ts +2 -18
- package/app/utils/limitConcurrency.ts +28 -0
- package/app/utils/listObjectsClient.ts +318 -0
- package/app/utils/listingLimits.ts +7 -0
- package/app/utils/loadConnectionLevel.ts +50 -0
- package/app/utils/localFilesStore/useFileStore.ts +1 -6
- package/app/utils/pathUtils.ts +65 -0
- package/app/utils/resourceId.ts +14 -29
- package/app/utils/s3HostAllowlist.ts +116 -0
- package/app/utils/signedFetch.ts +159 -24
- package/package.json +3 -3
- package/prisma/seed.ts +3 -3
- package/public/duckdb-extensions/checksums.json +12 -0
- package/public/duckdb-extensions/v1.4.3/wasm_eh/httpfs.duckdb_extension.wasm +0 -0
- package/public/duckdb-extensions/v1.4.3/wasm_eh/parquet.duckdb_extension.wasm +0 -0
- package/public/duckdb-extensions/v1.4.3/wasm_eh/spatial.duckdb_extension.wasm +0 -0
- package/public/duckdb-extensions/v1.4.3/wasm_mvp/httpfs.duckdb_extension.wasm +0 -0
- package/public/duckdb-extensions/v1.4.3/wasm_mvp/parquet.duckdb_extension.wasm +0 -0
- package/public/duckdb-extensions/v1.4.3/wasm_mvp/spatial.duckdb_extension.wasm +0 -0
- package/public/duckdb-extensions/v1.4.3/wasm_threads/httpfs.duckdb_extension.wasm +0 -0
- package/public/duckdb-extensions/v1.4.3/wasm_threads/parquet.duckdb_extension.wasm +0 -0
- package/public/duckdb-extensions/v1.4.3/wasm_threads/spatial.duckdb_extension.wasm +0 -0
- package/scripts/download-duckdb-extensions.mjs +188 -0
- package/scripts/prebuild.mjs +18 -10
- package/app/.server/auth/getPresignedUrl.ts +0 -21
- package/app/.server/auth/getS3Client.ts +0 -93
- package/app/routes/presign.route.tsx +0 -42
- 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
|
-
};
|
package/app/utils/getObjects.ts
DELETED
|
@@ -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
|
-
};
|