diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml index ad14db6e43..057895795d 100644 --- a/dev/docker-compose.yaml +++ b/dev/docker-compose.yaml @@ -519,6 +519,7 @@ services: - REGION=cockroach - QUEUE_CONFIG=${QUEUE_CONFIG} - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318/v1/traces + - SECURE=true restart: unless-stopped hulylake: image: hardcoreeng/hulylake diff --git a/foundations/core/packages/storage-client/src/client/datalake.ts b/foundations/core/packages/storage-client/src/client/datalake.ts index 24c9476d13..ad1dc1f7aa 100644 --- a/foundations/core/packages/storage-client/src/client/datalake.ts +++ b/foundations/core/packages/storage-client/src/client/datalake.ts @@ -17,6 +17,11 @@ import { concatLink } from '@hcengineering/core' import { FileStorage, FileStorageUploadOptions } from '../types' import { uploadMultipart, uploadXhr } from '../upload' +const getPathname = (url: string): string => { + const base = window?.location?.href !== undefined ? window.location.href : 'http://localhost' + return new URL(url, base).pathname +} + /** @public */ export class DatalakeStorage implements FileStorage { constructor (private readonly baseUrl: string) {} @@ -26,6 +31,11 @@ export class DatalakeStorage implements FileStorage { return concatLink(this.baseUrl, path) } + getCookiePath (workspace: string): string { + const url = concatLink(this.baseUrl, `/blob/${workspace}`) + return getPathname(url) + } + async getFileMeta (token: string, workspace: string, file: string): Promise> { const url = concatLink(this.baseUrl, `/meta/${encodeURIComponent(workspace)}/${encodeURIComponent(file)}`) try { diff --git a/foundations/core/packages/storage-client/src/client/front.ts b/foundations/core/packages/storage-client/src/client/front.ts index 67ade0229c..54fa4cd0b9 100644 --- a/foundations/core/packages/storage-client/src/client/front.ts +++ b/foundations/core/packages/storage-client/src/client/front.ts @@ -17,6 +17,11 @@ import { concatLink } from '@hcengineering/core' import { FileStorage, FileStorageUploadOptions } from '../types' import { uploadXhr } from '../upload' +const getPathname = (url: string): string => { + const base = window?.location?.href !== undefined ? window.location.href : 'http://localhost' + return new URL(url, base).pathname +} + /** @public */ export class FrontStorage implements FileStorage { constructor (private readonly baseUrl: string) {} @@ -26,6 +31,11 @@ export class FrontStorage implements FileStorage { return concatLink(this.baseUrl, path) } + getCookiePath (workspace: string): string { + const url = concatLink(this.baseUrl, `/${workspace}`) + return getPathname(url) + } + async getFileMeta (token: string, workspace: string, file: string): Promise> { return {} } diff --git a/foundations/core/packages/storage-client/src/client/hulylake.ts b/foundations/core/packages/storage-client/src/client/hulylake.ts index 397815c015..00acdb31f6 100644 --- a/foundations/core/packages/storage-client/src/client/hulylake.ts +++ b/foundations/core/packages/storage-client/src/client/hulylake.ts @@ -17,6 +17,11 @@ import { concatLink } from '@hcengineering/core' import { FileStorage, FileStorageUploadOptions } from '../types' import { uploadXhr } from '../upload' +const getPathname = (url: string): string => { + const base = window?.location?.href !== undefined ? window.location.href : 'http://localhost' + return new URL(url, base).pathname +} + /** @public */ export class HulylakeStorage implements FileStorage { constructor (private readonly baseUrl: string) {} @@ -26,6 +31,11 @@ export class HulylakeStorage implements FileStorage { return concatLink(this.baseUrl, path) } + getCookiePath (workspace: string): string { + const url = concatLink(this.baseUrl, `/api/${workspace}`) + return getPathname(url) + } + async getFileMeta (token: string, workspace: string, file: string): Promise> { return {} } diff --git a/foundations/core/packages/storage-client/src/types.ts b/foundations/core/packages/storage-client/src/types.ts index bcbe88be8f..45924bc3dd 100644 --- a/foundations/core/packages/storage-client/src/types.ts +++ b/foundations/core/packages/storage-client/src/types.ts @@ -29,6 +29,7 @@ export interface FileStorageUploadOptions { /** @public */ export interface FileStorage { getFileUrl: (workspace: string, file: string, filename?: string) => string + getCookiePath: (workspace: string) => string getFileMeta: (token: string, workspace: string, file: string) => Promise> uploadFile: ( token: string, diff --git a/packages/presentation/src/utils.ts b/packages/presentation/src/utils.ts index 43b320c19e..8771da2ab4 100644 --- a/packages/presentation/src/utils.ts +++ b/packages/presentation/src/utils.ts @@ -891,15 +891,19 @@ export function isSpaceClass (_class: Ref>): boolean { } export function setPresentationCookie (token: string, workspaceUuid: WorkspaceUuid): void { - function setToken (path: string): void { - const res = - encodeURIComponent(plugin.metadata.Token.replaceAll(':', '-')) + - '=' + - encodeURIComponent(token) + - `; path=${path}` - document.cookie = res + const cookieName = encodeURIComponent(plugin.metadata.Token.replaceAll(':', '-')) + const cookieValue = encodeURIComponent(token) + + const storage = getMetadata(plugin.metadata.FileStorage) + if (storage !== undefined) { + let path = `/files/${workspaceUuid}` + try { + path = storage.getCookiePath(workspaceUuid) + } catch {} + + const normalized = path.startsWith('/') ? path : `/${path}` + document.cookie = `${cookieName}=${cookieValue}; path=${normalized}` } - setToken('/files/' + workspaceUuid) } export const upgradeDownloadProgress = writable(-1) diff --git a/services/datalake/pod-datalake/src/config.ts b/services/datalake/pod-datalake/src/config.ts index af85152bf0..70b16b0781 100644 --- a/services/datalake/pod-datalake/src/config.ts +++ b/services/datalake/pod-datalake/src/config.ts @@ -35,6 +35,7 @@ export interface Config { DbUrl: string Buckets: BucketConfig[] CleanupInterval: number + Secure: boolean Readonly: boolean Cache: CacheConfig } @@ -86,6 +87,7 @@ const config: Config = (() => { AccountsUrl: process.env.ACCOUNTS_URL, DbUrl: process.env.DB_URL, Buckets: parseBucketsConfig(process.env.BUCKETS), + Secure: process.env.SECURE === 'true', Readonly: process.env.READONLY === 'true', Cache: { enabled: process.env.CACHE_ENABLED !== 'false', diff --git a/services/datalake/pod-datalake/src/middleware.ts b/services/datalake/pod-datalake/src/middleware.ts index 986cdc98bb..f59a1910c5 100644 --- a/services/datalake/pod-datalake/src/middleware.ts +++ b/services/datalake/pod-datalake/src/middleware.ts @@ -38,6 +38,14 @@ export const keepAlive = (options: KeepAliveOptions): RequestHandler => { } } +export const withOptionalAuth = (secure: boolean): RequestHandler => { + return secure + ? withAuthorization + : (req: Request, res: Response, next: NextFunction) => { + next() + } +} + export const withAdminAuthorization = (req: RequestWithAuth, res: Response, next: NextFunction): void => { try { const token = extractToken(req.headers) diff --git a/services/datalake/pod-datalake/src/server.ts b/services/datalake/pod-datalake/src/server.ts index 7cac5cc190..ac5d634509 100644 --- a/services/datalake/pod-datalake/src/server.ts +++ b/services/datalake/pod-datalake/src/server.ts @@ -36,7 +36,8 @@ import { withAuthorization, withBlob, withWorkspace, - withReadonly + withReadonly, + withOptionalAuth } from './middleware' import { handleBlobDelete, @@ -176,13 +177,33 @@ export async function createServer ( app.get('/blob/:workspace', withAdminAuthorization, withWorkspace, wrapRequest(ctx, 'listBlobs', handleBlobList)) - app.head('/blob/:workspace/:name', withBlob, wrapRequest(ctx, 'headBlob', handleBlobHead)) + app.head( + '/blob/:workspace/:name', + withOptionalAuth(config.Secure), + withBlob, + wrapRequest(ctx, 'headBlob', handleBlobHead) + ) - app.head('/blob/:workspace/:name/:filename', withBlob, wrapRequest(ctx, 'headBlob', handleBlobHead)) + app.head( + '/blob/:workspace/:name/:filename', + withOptionalAuth(config.Secure), + withBlob, + wrapRequest(ctx, 'headBlob', handleBlobHead) + ) - app.get('/blob/:workspace/:name', withBlob, wrapRequest(ctx, 'getBlob', handleBlobGet)) + app.get( + '/blob/:workspace/:name', + withOptionalAuth(config.Secure), + withBlob, + wrapRequest(ctx, 'getBlob', handleBlobGet) + ) - app.get('/blob/:workspace/:name/:filename', withBlob, wrapRequest(ctx, 'getBlob', handleBlobGet)) + app.get( + '/blob/:workspace/:name/:filename', + withOptionalAuth(config.Secure), + withBlob, + wrapRequest(ctx, 'getBlob', handleBlobGet) + ) app.delete('/blob/:workspace/:name', withAuthorization, withBlob, wrapRequest(ctx, 'deleteBlob', handleBlobDelete)) @@ -206,7 +227,12 @@ export async function createServer ( // Blob meta - app.get('/meta/:workspace/:name', withBlob, wrapRequest(ctx, 'getMeta', handleMetaGet)) + app.get( + '/meta/:workspace/:name', + withOptionalAuth(config.Secure), + withBlob, + wrapRequest(ctx, 'getMeta', handleMetaGet) + ) app.put('/meta/:workspace/:name', withAuthorization, withBlob, wrapRequest(ctx, 'putMeta', handleMetaPut))